diff --git a/.babelrc.js b/.babelrc.js index 2fa95d0716e..52fd762007e 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -14,16 +14,8 @@ module.exports = { [ useLocal('@babel/preset-env'), { - "targets": { - "browsers": [ - "chrome >= 61", - "safari >=8", - "edge >= 14", - "ff >= 57", - "ie >= 11", - "ios >= 8" - ] - } + "useBuiltIns": "entry", + "corejs": "3.13.0" } ] ], diff --git a/.circleci/config.yml b/.circleci/config.yml index ea5fb916a91..784b520ecc1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,11 +3,11 @@ # Check https://circleci.com/docs/2.0/language-javascript/ for more details # -aliases: +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 @@ -52,7 +52,7 @@ aliases: - &unit_test_steps - checkout - restore_cache: *restore_dep_cache - - run: npm install + - run: npm ci - save_cache: *save_dep_cache - run: *install - run: *setup_browserstack @@ -72,7 +72,7 @@ jobs: build: <<: *environment steps: *unit_test_steps - + e2etest: <<: *environment steps: *endtoend_test_steps diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000000..cfb29ebdfa9 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,11 @@ +ARG VARIANT="12" +FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - +RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list +RUN apt update +RUN apt install -y google-chrome-stable xvfb diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..0176b8317b3 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,27 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/main/containers/javascript-node +{ + "name": "Ubuntu", + + "build": { + "dockerfile": "Dockerfile", + "args": { "VARIANT": "12" } + }, + + "postCreateCommand": "bash .devcontainer/postCreate.sh", + + // Set *default* container specific settings.json values on container create. + "settings": {}, + + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "nickdodd79.gulptasks" + ], + + // 9999 is web server, 9876 is karma + "forwardPorts": [9876, 9999], + + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "node" +} diff --git a/.devcontainer/postCreate.sh b/.devcontainer/postCreate.sh new file mode 100644 index 00000000000..7e14a2d200d --- /dev/null +++ b/.devcontainer/postCreate.sh @@ -0,0 +1,6 @@ +echo "Post Create Starting" + +nvm install +nvm use +npm install gulp-cli -g +npm ci diff --git a/.eslintrc.js b/.eslintrc.js index c64ab379c52..d3379d70919 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,50 +1,60 @@ -const allowedModules = require("./allowedModules"); +const allowedModules = require('./allowedModules.js'); module.exports = { - "env": { - "browser": true, - "commonjs": true + env: { + browser: true, + commonjs: true }, - "settings": { - "import/resolver": { - "node": { - "moduleDirectory": ["node_modules", "./"] + settings: { + 'import/resolver': { + node: { + moduleDirectory: ['node_modules', './'] } } }, - "extends": "standard", - "plugins": [ - "prebid", - "import" + extends: 'standard', + plugins: [ + 'prebid', + 'import' ], - "globals": { - "$$PREBID_GLOBAL$$": false + globals: { + '$$PREBID_GLOBAL$$': false, + 'BROWSERSTACK_USERNAME': false, + 'BROWSERSTACK_KEY': false }, - "parserOptions": { - "sourceType": "module" + // use babel as parser for fancy syntax + parser: '@babel/eslint-parser', + parserOptions: { + sourceType: 'module', + ecmaVersion: 2018, }, - "rules": { - "comma-dangle": "off", - "semi": "off", - "space-before-function-paren": "off", - "import/extensions": ["error", "ignorePackages"], + + rules: { + 'comma-dangle': 'off', + semi: 'off', + 'space-before-function-paren': 'off', + 'import/extensions': ['error', 'ignorePackages'], // Exceptions below this line are temporary, so that eslint can be added into the CI process. // Violations of these styles should be fixed, and the exceptions removed over time. // // See Issue #1111. - "eqeqeq": "off", - "no-return-assign": "off", - "no-throw-literal": "off", - "no-undef": 2, - "no-useless-escape": "off", - "no-console": "error" + eqeqeq: 'off', + 'no-return-assign': 'off', + 'no-throw-literal': 'off', + 'no-undef': 2, + 'no-useless-escape': 'off', + 'no-console': 'error' }, - "overrides": Object.keys(allowedModules).map((key) => ({ - "files": key + "/**/*.js", - "rules": { - "prebid/validate-imports": ["error", allowedModules[key]] + overrides: Object.keys(allowedModules).map((key) => ({ + files: key + '/**/*.js', + rules: { + 'prebid/validate-imports': ['error', allowedModules[key]] } - })) + })).concat([{ + // code in other packages (such as plugins/eslint) is not "seen" by babel and its parser will complain. + files: 'plugins/*/**/*.js', + parser: 'esprima' + }]) }; diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 8984252f4c3..a3246cffd6d 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -22,7 +22,7 @@ version-resolver: patch: labels: - 'patch' - default: patch + default: minor template: | ## In This Release $CHANGES diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 84131c177a3..1152e2942bf 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -1,4 +1,5 @@ ## Summary + We take PR review seriously. Please read https://medium.com/@mrjoelkemp/giving-better-code-reviews-16109e0fdd36#.xa8lc4i23 to understand how a PR review should be conducted. Be rational and strict in your review, make sure you understand exactly what the submitter's intent is. Anyone in the community can review a PR, but a Prebid Org member is also required. A Prebid Org member should take ownership of a PR and do the initial review. If the PR is for a standard bid adapter or a standard analytics adapter, just the one review from a core member is sufficient. The reviewer will check against [required conventions](http://prebid.org/dev-docs/bidder-adaptor.html#required-adapter-conventions) and may merge the PR after approving and confirming that the documentation PR against prebid.org is open and linked to the issue. @@ -11,14 +12,15 @@ General gulp commands include separate commands for serving the codebase on a bu - Run `gulp review-start`, adding the host parameter `gulp review-start --host=0.0.0.0` will bind to all IPs on the machine - A page will open which provides a hub for common reviewer tools. - - If you need to manually acceess the tools: + - If you need to manually access the tools: - Navigate to build/coverage/lcov-report/index.html to view coverage - Navigate to integrationExamples/gpt/hellow_world.html for basic integration testing - - The hello_world.html and other exampls can be edited and used as needed to verify functionality + - The hello_world.html and other examples can be edited and used as needed to verify functionality ### General PR review Process + - All required global and bidder-adapter rules defined in the [Module Rules](https://docs.prebid.org/dev-docs/module-rules.html) must be followed. Please review these rules often - we depend on reviewers to enforce them. -- Checkout the branch (these instructions are available on the github PR page as well). +- Checkout the branch (these instructions are available on the GitHub PR page as well). - Verify PR is a single change type. Example, refactor OR bugfix. If more than 1 type, ask submitter to break out requests. - Verify code under review has at least 80% unit test coverage. If legacy code doesn't have enough unit test coverage, require that additional unit tests to be included in the PR. - Verify tests are green in Travis-ci + local build by running `gulp serve` | `gulp test` @@ -29,13 +31,14 @@ General gulp commands include separate commands for serving the codebase on a bu - If the change results in needing updates to docs (such as public API change, module interface etc), add a label for "needs docs" and inform the submitter they must submit a docs PR to update the appropriate area of Prebid.org **before the PR can merge**. Help them with finding where the docs are located on prebid.org if needed. - If all above is good, add a `LGTM` comment and, if the change is in PBS-core or is an important module like the prebidServerBidAdapter, request 1 additional core member to review. - Once there are 2 `LGTM` on the PR, merge to master -- The [draft release](https://github.com/prebid/Prebid.js/releases) notes are managed by [release drafter](https://github.com/release-drafter/release-drafter). To get the PR added to the release notes do the steps below. A github action will use that information to build the release notes. +- The [draft release](https://github.com/prebid/Prebid.js/releases) notes are managed by [release drafter](https://github.com/release-drafter/release-drafter). To get the PR added to the release notes do the steps below. A GitHub action will use that information to build the release notes. - Adjust the PR Title to be appropriate for release notes - Add a label for `feature`, `maintenance`, `fix`, `bugfix` or `bug` to categorize the PR - - Add a semver label of `major`, `minor` or `patch` to indicate the scope of change + - Add a SemVer label of `major`, `minor` or `patch` to indicate the scope of change ### Reviewing a New or Updated Bid Adapter -Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/bidder-adaptor.html + +Documentation: https://docs.prebid.org/dev-docs/bidder-adaptor.html Follow steps above for general review process. In addition, please verify the following: - Verify the biddercode and aliases are valid: @@ -67,14 +70,16 @@ Follow steps above for general review process. In addition, please verify the fo - After a new adapter is approved, let the submitter know they may open a PR in the [headerbid-expert repository](https://github.com/prebid/headerbid-expert) to have their adapter recognized by the [Headerbid Expert extension](https://chrome.google.com/webstore/detail/headerbid-expert/cgfkddgbnfplidghapbbnngaogeldmop). The PR should be to the [bidder patterns file](https://github.com/prebid/headerbid-expert/blob/master/bidderPatterns.js), adding an entry with their adapter's name and the url the adapter uses to send and receive bid responses. ### Reviewing a New or Updated Analytics Adapter -Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/integrate-with-the-prebid-analytics-api.html + +Documentation: https://docs.prebid.org/dev-docs/integrate-with-the-prebid-analytics-api.html No additional steps above the general review process and making sure it conforms to the [Module Rules](https://docs.prebid.org/dev-docs/module-rules.html). Make sure there's a docs pull request ### Reviewing a New or Updated User ID Sub-Module -Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/modules/userId.html#id-providers + +Documentation: https://docs.prebid.org/dev-docs/modules/userId.html#id-providers Follow steps above for general review process. In addition: - Try running the new user ID module with a basic config and confirm it hits the endpoint and stores the results. @@ -102,9 +107,11 @@ Follow steps above for general review process. In addition: - make sure there's a docs pull request ### Reviewing a New or Updated Real-Time-Data Sub-Module -Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/add-rtd-submodule.html + +Documentation: https://docs.prebid.org/dev-docs/add-rtd-submodule.html Follow steps above for general review process. In addition: + - The RTD Provider must include a `providerRtdProvider.md` file. This file must have example parameters and document a sense of what to expect: what should change in the bidrequest, or what targeting data should be added? - Try running the new sub-module and confirm the provided test parameters. - Confirm that the module @@ -118,9 +125,7 @@ Follow steps above for general review process. In addition: ## Ticket Coordinator -Each week, Prebid Org assigns one person to keep an eye on incoming issues and PRs. Every Monday morning a reminder is -sent to the prebid-js slack channel with a link to the spreadsheet. If you're on rotation, please check that list each -Monday to see if you're on-duty. +Each week, Prebid Org assigns one person to keep an eye on incoming issues and PRs. Every Monday morning a reminder is sent to the prebid-js slack channel with a link to the spreadsheet. If you're on rotation, please check that list each Monday to see if you're on-duty. When on-duty: - Review issues and PRs at least once per weekday for new items. Encourage a 48 "SLA" on PRs/issues assigned. Aim for touchpoint once every 48/hours. diff --git a/README.md b/README.md index a220dd69f66..42d747b20b6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ [![Build Status](https://circleci.com/gh/prebid/Prebid.js.svg?style=svg)](https://circleci.com/gh/prebid/Prebid.js) [![Percentage of issues still open](http://isitmaintained.com/badge/open/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open") -[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Average time to resolve an issue") [![Code Climate](https://codeclimate.com/github/prebid/Prebid.js/badges/gpa.svg)](https://codeclimate.com/github/prebid/Prebid.js) [![Coverage Status](https://coveralls.io/repos/github/prebid/Prebid.js/badge.svg)](https://coveralls.io/github/prebid/Prebid.js) [![devDependencies Status](https://david-dm.org/prebid/Prebid.js/dev-status.svg)](https://david-dm.org/prebid/Prebid.js?type=dev) @@ -60,6 +59,8 @@ module.exports = { loader: 'babel-loader', // presets and plugins for Prebid.js must be manually specified separate from your other babel rule. // this can be accomplished by requiring prebid's .babelrc.js file (requires Babel 7 and Node v8.9.0+) + // as of Prebid 6, babelrc.js only targets modern browsers. One can change the targets and build for + // older browsers if they prefer, but integration tests on ie11 were removed in Prebid.js 6.0 options: require('prebid.js/.babelrc.js') } } @@ -129,16 +130,22 @@ Once setup, run the following command to globally install the `gulp-cli` package ## Build for Development -To build the project on your local machine, run: +To build the project on your local machine we recommend, running: - $ gulp serve + $ gulp serve-and-test --file -This runs some code quality checks, starts a web server at `http://localhost:9999` serving from the project root and generates the following files: +This will run testing but not linting. A web server will start at `http://localhost:9999` serving from the project root and generates the following files: + `./build/dev/prebid.js` - Full source code for dev and debug + `./build/dev/prebid.js.map` - Source map for dev and debug -+ `./build/dist/prebid.js` - Minified production code -+ `./prebid.js_.zip` - Distributable zip archive ++ `./build/dev/prebid-core.js` ++ `./build/dev/prebid-core.js.map` + + +Development may be a bit slower but if you prefer linting and additional watch files you can also still run just: + + $ gulp serve + ### Build Optimization @@ -152,7 +159,7 @@ Building with just these adapters will result in a smaller bundle which should a **Build standalone prebid.js** -- Clone the repo, run `npm install` +- Clone the repo, run `npm ci` - Then run the build: $ gulp build --modules=openxBidAdapter,rubiconBidAdapter,sovrnBidAdapter @@ -273,7 +280,7 @@ As you make code changes, the bundles will be rebuilt and the page reloaded auto ## Contribute -Many SSPs, bidders, and publishers have contributed to this project. [Hundreds of bidders](https://github.com/prebid/Prebid.js/tree/master/src/adapters) are supported by Prebid.js. +Many SSPs, bidders, and publishers have contributed to this project. [Hundreds of bidders](https://github.com/prebid/Prebid.js/tree/master/modules) are supported by Prebid.js. For guidelines, see [Contributing](./CONTRIBUTING.md). @@ -315,7 +322,7 @@ For instructions on writing tests for Prebid.js, see [Testing Prebid.js](http:// ### Supported Browsers -Prebid.js is supported on IE11 and modern browsers. +Prebid.js is supported on IE11 and modern browsers until 5.x. 6.x+ transpiles to target >0.25%; not Opera Mini; not IE11. ### Governance Review our governance model [here](https://github.com/prebid/Prebid.js/tree/master/governance.md). diff --git a/allowedModules.js b/allowedModules.js index d8e8b69f593..81920cdc15f 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -5,14 +5,14 @@ const sharedWhiteList = [ '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/features/array/from', // no ie11 + 'core-js-pure/web/url-search-params' // no ie11 ]; module.exports = { 'modules': [ ...sharedWhiteList, 'criteo-direct-rsa-validate', - 'jsencrypt', 'crypto-js', 'live-connect' // Maintained by LiveIntent : https://github.com/liveintent-berlin/live-connect/ ], diff --git a/browsers.json b/browsers.json index f73bbbb8aac..bd6bd5772d6 100644 --- a/browsers.json +++ b/browsers.json @@ -1,74 +1,51 @@ { - "bs_edge_17_windows_10": { + "bs_edge_latest_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "edge", - "browser_version": "17.0", + "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_edge_18_windows_10": { - "base": "BrowserStack", - "os_version": "10", - "browser": "edge", - "browser_version": "18.0", - "device": null, - "os": "Windows" - }, - "bs_ie_11_windows_10": { - "base": "BrowserStack", - "os_version": "10", - "browser": "ie", - "browser_version": "11.0", - "device": null, - "os": "Windows" - }, - "bs_chrome_89_windows_10": { + "bs_chrome_latest_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "89.0", + "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_chrome_79_windows_10": { + "bs_chrome_87_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "79.0", + "browser_version": "87.0", "device": null, "os": "Windows" }, - "bs_firefox_73_windows_10": { + "bs_firefox_latest_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "firefox", - "browser_version": "73.0", + "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_firefox_72_windows_10": { + "bs_safari_latest_mac_bigsur": { "base": "BrowserStack", - "os_version": "10", - "browser": "firefox", - "browser_version": "72.0", - "device": null, - "os": "Windows" - }, - "bs_safari_11_mac_catalina": { - "base": "BrowserStack", - "os_version": "Catalina", + "os_version": "Big Sur", "browser": "safari", - "browser_version": "13.0", + "browser_version": "latest", "device": null, "os": "OS X" }, - "bs_safari_12_mac_mojave": { + "bs_safari_15_catalina": { "base": "BrowserStack", - "os_version": "Mojave", + "os_version": "Catalina", "browser": "safari", - "browser_version": "12.0", + "browser_version": "13.1", "device": null, "os": "OS X" } + } diff --git a/gulpHelpers.js b/gulpHelpers.js index bcaf3736f15..c0946edf93d 100644 --- a/gulpHelpers.js +++ b/gulpHelpers.js @@ -34,7 +34,6 @@ module.exports = { }, jsonifyHTML: function (str) { - console.log(arguments); return str.replace(/\n/g, '') .replace(/<\//g, '<\\/') .replace(/\/>/g, '\\/>'); diff --git a/gulpfile.js b/gulpfile.js index e2bed2a660f..6ecfee1b672 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -8,13 +8,13 @@ var gutil = require('gulp-util'); var connect = require('gulp-connect'); var webpack = require('webpack'); var webpackStream = require('webpack-stream'); -var uglify = require('gulp-uglify'); +var terser = require('gulp-terser'); var gulpClean = require('gulp-clean'); var KarmaServer = require('karma').Server; -var karmaConfMaker = require('./karma.conf.maker'); +var karmaConfMaker = require('./karma.conf.maker.js'); var opens = require('opn'); -var webpackConfig = require('./webpack.conf'); -var helpers = require('./gulpHelpers'); +var webpackConfig = require('./webpack.conf.js'); +var helpers = require('./gulpHelpers.js'); var concat = require('gulp-concat'); var header = require('gulp-header'); var footer = require('gulp-footer'); @@ -71,7 +71,14 @@ function lint(done) { const isFixed = function (file) { return file.eslint != null && file.eslint.fixed; } - return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js'], { base: './' }) + return gulp.src([ + 'src/**/*.js', + 'modules/**/*.js', + 'test/**/*.js', + 'plugins/**/*.js', + '!plugins/**/node_modules/**', + './*.js' + ], { base: './' }) .pipe(gulpif(argv.nolintfix, eslint(), eslint({ fix: true }))) .pipe(eslint.format('stylish')) .pipe(eslint.failAfterError()) @@ -108,38 +115,6 @@ function viewReview(done) { viewReview.displayName = 'view-review'; -// Watch Task with Live Reload -function watch(done) { - var mainWatcher = gulp.watch([ - 'src/**/*.js', - 'modules/**/*.js', - 'test/spec/**/*.js', - '!test/spec/loaders/**/*.js' - ]); - var loaderWatcher = gulp.watch([ - 'loaders/**/*.js', - 'test/spec/loaders/**/*.js' - ]); - - connect.server({ - https: argv.https, - port: port, - host: FAKE_SERVER_HOST, - root: './', - livereload: true - }); - - mainWatcher.on('all', gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test))); - loaderWatcher.on('all', gulp.series(lint)); - done(); -}; - -function makeModuleList(modules) { - return modules.map(module => { - return '"' + module + '"' - }); -} - function makeDevpackPkg() { var cloned = _.cloneDeep(webpackConfig); cloned.devtool = 'source-map'; @@ -151,7 +126,6 @@ function makeDevpackPkg() { return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) - .pipe(replace(/('|")v\$prebid\.modulesList\$('|")/g, makeModuleList(externalModules))) .pipe(gulp.dest('build/dev')) .pipe(connect.reload()); } @@ -168,8 +142,7 @@ function makeWebpackPkg() { return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) - .pipe(uglify()) - .pipe(replace(/('|")v\$prebid\.modulesList\$('|")/g, makeModuleList(externalModules))) + .pipe(terser()) .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid }))) .pipe(gulp.dest('build/dist')); } @@ -248,60 +221,68 @@ function bundle(dev, moduleArr) { // If --browsers is given, browsers can be chosen explicitly. e.g. --browsers=chrome,firefox,ie9 // If --notest is given, it will immediately skip the test task (useful for developing changes with `gulp serve --notest`) -function test(done) { - if (argv.notest) { - done(); - } else if (argv.e2e) { - let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio'); - let wdioConf = path.join(__dirname, 'wdio.conf.js'); - let wdioOpts; - - if (argv.file) { - wdioOpts = [ - wdioConf, - `--spec`, - `${argv.file}` - ] - } else { - wdioOpts = [ - wdioConf - ]; - } +function testTaskMaker(options = {}) { + ['watch', 'e2e', 'file', 'browserstack', 'notest'].forEach(opt => { + options[opt] = options[opt] || argv[opt]; + }) - // run fake-server - const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]); - fakeServer.stdout.on('data', (data) => { - console.log(`stdout: ${data}`); - }); - fakeServer.stderr.on('data', (data) => { - console.log(`stderr: ${data}`); - }); + return function test(done) { + if (options.notest) { + done(); + } else if (options.e2e) { + let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio'); + let wdioConf = path.join(__dirname, 'wdio.conf.js'); + let wdioOpts; + + if (options.file) { + wdioOpts = [ + wdioConf, + `--spec`, + `${options.file}` + ] + } else { + wdioOpts = [ + wdioConf + ]; + } - execa(wdioCmd, wdioOpts, { stdio: 'inherit' }) - .then(stdout => { - // kill fake server - fakeServer.kill('SIGINT'); - done(); - process.exit(0); - }) - .catch(err => { - // kill fake server - fakeServer.kill('SIGINT'); - done(new Error(`Tests failed with error: ${err}`)); - process.exit(1); + // run fake-server + const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]); + fakeServer.stdout.on('data', (data) => { + console.log(`stdout: ${data}`); + }); + fakeServer.stderr.on('data', (data) => { + console.log(`stderr: ${data}`); }); - } else { - var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file); - var browserOverride = helpers.parseBrowserArgs(argv); - if (browserOverride.length > 0) { - karmaConf.browsers = browserOverride; - } + execa(wdioCmd, wdioOpts, { stdio: 'inherit' }) + .then(stdout => { + // kill fake server + fakeServer.kill('SIGINT'); + done(); + process.exit(0); + }) + .catch(err => { + // kill fake server + fakeServer.kill('SIGINT'); + done(new Error(`Tests failed with error: ${err}`)); + process.exit(1); + }); + } else { + var karmaConf = karmaConfMaker(false, options.browserstack, options.watch, options.file); - new KarmaServer(karmaConf, newKarmaCallback(done)).start(); + var browserOverride = helpers.parseBrowserArgs(argv); + if (browserOverride.length > 0) { + karmaConf.browsers = browserOverride; + } + + new KarmaServer(karmaConf, newKarmaCallback(done)).start(); + } } } +const test = testTaskMaker(); + function newKarmaCallback(done) { return function (exitCode) { if (exitCode) { @@ -378,6 +359,35 @@ function startFakeServer() { }); } +// Watch Task with Live Reload +function watchTaskMaker(options = {}) { + if (options.livereload == null) { + options.livereload = true; + } + options.alsoWatch = options.alsoWatch || []; + + return function watch(done) { + var mainWatcher = gulp.watch([ + 'src/**/*.js', + 'modules/**/*.js', + ].concat(options.alsoWatch)); + + connect.server({ + https: argv.https, + port: port, + host: FAKE_SERVER_HOST, + root: './', + livereload: options.livereload + }); + + mainWatcher.on('all', options.task()); + done(); + } +} + +const watch = watchTaskMaker({alsoWatch: ['test/**/*.js'], task: () => gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test))}); +const watchFast = watchTaskMaker({livereload: false, task: () => gulp.series('build-bundle-dev')}); + // support tasks gulp.task(lint); gulp.task(watch); @@ -390,7 +400,8 @@ gulp.task('build-bundle-dev', gulp.series(makeDevpackPkg, gulpBundle.bind(null, gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg, gulpBundle.bind(null, false))); // public tasks (dependencies are needed for each task since they can be ran on their own) -gulp.task('test', gulp.series(clean, lint, test)); +gulp.task('test-only', test); +gulp.task('test', gulp.series(clean, lint, 'test-only')); gulp.task('test-coverage', gulp.series(clean, testCoverage)); gulp.task(viewCoverage); @@ -401,7 +412,8 @@ gulp.task('build', gulp.series(clean, 'build-bundle-prod')); gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid)); gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test))); -gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watch))); +gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast))); +gulp.task('serve-and-test', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast, testTaskMaker({watch: true})))); gulp.task('serve-fake', gulp.series(clean, gulp.parallel('build-bundle-dev', watch), injectFakeServerEndpointDev, test, startFakeServer)); gulp.task('default', gulp.series(clean, makeWebpackPkg)); diff --git a/integrationExamples/gpt/adloox.html b/integrationExamples/gpt/adloox.html index 33f8b9be6a2..e8920cf2ee1 100644 --- a/integrationExamples/gpt/adloox.html +++ b/integrationExamples/gpt/adloox.html @@ -64,9 +64,11 @@ playerSize: [ 640, 480 ] } }, - fpd: { - context: { - pbAdSlot: '/19968336/prebid_cache_video_adunit' + ortb2Imp: { + ext: { + data: { + pbadslot: '/19968336/prebid_cache_video_adunit' + } } }, bids: [ @@ -106,12 +108,14 @@ pbjs.initAdserverSet = true; googletag.cmd.push(function() { - pbjs.setTargetingForGPTAsync && pbjs.setTargetingForGPTAsync(adUnits); + const adUnitCodes = adUnits.map(adUnit => adUnit.code); + pbjs.setTargetingForGPTAsync(adUnitCodes); googletag.pubads().refresh(); }); var videoBids = bids[videoAdUnit.code]; if (videoBids) { +// DEMO NOTES: your environment likely will use the commented section //// var videoUrl = videoBids.bids[0].vastUrl; // var videoUrl = pbjs.adServers.dfp.buildVideoUrl({ // adUnit: videoAdUnit, @@ -124,17 +128,34 @@ // output: 'vast' // } // }); - invokeVideoPlayer(videoUrl); +////////////////////////////////////////////////////////////////////////// + var ret = pbjs.adServers.adloox.buildVideoUrl({ + adUnit: videoAdUnit, + url: videoUrl + }, invokeVideoPlayer); + if (!ret) console.log('Error building Adloox video URL'); } } // optionally wrap with googletag to have gpt-pre-auction - // automatically populate Prebid Ad Slot (pbAdSlot) + // automatically populate Prebid Ad Slot (pbadslot) // https://docs.prebid.org/dev-docs/modules/gpt-pre-auction.html - // alternatively remove wrapping and set AdUnit.fpd.context.pbAdSlot + // alternatively remove wrapping and set AdUnit.ortb2Imp.ext.data.pbadslot googletag.cmd.push(function() { pbjs.que.push(function() { pbjs.setConfig({ + realTimeData: { + auctionDelay: AUCTION_DELAY, + dataProviders: [ + { + name: 'adloox', + params: { // optional, defaults shown + thresholds: [ 50, 60, 70, 80, 90 ], + slotinpath: false + } + } + ] + }, instreamTracking: { enabled: true }, diff --git a/integrationExamples/gpt/afpExample.html b/integrationExamples/gpt/afpExample.html new file mode 100644 index 00000000000..a1e6e800d69 --- /dev/null +++ b/integrationExamples/gpt/afpExample.html @@ -0,0 +1,242 @@ + + + + + Prebid.js Banner Example + + + + +

In-image

+
+
+ +
+ +
+ +

In-image Max

+
+
+ +
+ +
+ +

In-content Banner

+
+
+ +
+ +

In-content Stories

+
+
+ +
+ +

Action Scroller

+
+
+ +
+ +

Action Scroller Light

+
+
+ +
+ +

Just Banner

+
+
+ +
+ +

In-content Video

+
+
+ +
+ + + + + diff --git a/integrationExamples/gpt/afpGamExample.html b/integrationExamples/gpt/afpGamExample.html new file mode 100644 index 00000000000..64cb169893c --- /dev/null +++ b/integrationExamples/gpt/afpGamExample.html @@ -0,0 +1,152 @@ + + + + + Prebid.js Banner Example + + + + + +

In-image

+
+
+ +
+
+ +
+
+ +

In-content Video

+
+
+
+ +
+
+ +

Action Scroller

+
+
+
+ +
+
+ + diff --git a/integrationExamples/gpt/airgridRtdProvider_example.html b/integrationExamples/gpt/airgridRtdProvider_example.html new file mode 100644 index 00000000000..a8fd989f682 --- /dev/null +++ b/integrationExamples/gpt/airgridRtdProvider_example.html @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + +

AirGrid RTD Prebid

+ +
+ +
+ + AirGrid Audiences: +
+ + diff --git a/integrationExamples/gpt/akamaidap_email_example.html b/integrationExamples/gpt/akamaidap_email_example.html new file mode 100755 index 00000000000..828b2add787 --- /dev/null +++ b/integrationExamples/gpt/akamaidap_email_example.html @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+
User IDs Sent to Bidding Adapter
+
+ + diff --git a/integrationExamples/gpt/akamaidap_segments_example.html b/integrationExamples/gpt/akamaidap_segments_example.html new file mode 100644 index 00000000000..e85ac8e1337 --- /dev/null +++ b/integrationExamples/gpt/akamaidap_segments_example.html @@ -0,0 +1,132 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+
Segments Sent to Bidding Adapter
+
+ + diff --git a/integrationExamples/gpt/akamaidap_signature_example.html b/integrationExamples/gpt/akamaidap_signature_example.html new file mode 100644 index 00000000000..e4c7c617653 --- /dev/null +++ b/integrationExamples/gpt/akamaidap_signature_example.html @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+
User IDs Sent to Bidding Adapter
+
+ + diff --git a/integrationExamples/gpt/akamaidap_x1_example.html b/integrationExamples/gpt/akamaidap_x1_example.html new file mode 100755 index 00000000000..b1f16acc560 --- /dev/null +++ b/integrationExamples/gpt/akamaidap_x1_example.html @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+
User IDs Sent to Bidding Adapter
+
+ + diff --git a/integrationExamples/gpt/amp/creative.html b/integrationExamples/gpt/amp/creative.html index 86f669dd6b5..384b81107cc 100644 --- a/integrationExamples/gpt/amp/creative.html +++ b/integrationExamples/gpt/amp/creative.html @@ -1,38 +1,16 @@ + diff --git a/integrationExamples/gpt/gdpr_indefinite_timeout_exampe.html b/integrationExamples/gpt/gdpr_indefinite_timeout_exampe.html new file mode 100644 index 00000000000..39345fe69d7 --- /dev/null +++ b/integrationExamples/gpt/gdpr_indefinite_timeout_exampe.html @@ -0,0 +1,151 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + diff --git a/integrationExamples/gpt/haloRtdProvider_example.html b/integrationExamples/gpt/hadronRtdProvider_example.html similarity index 87% rename from integrationExamples/gpt/haloRtdProvider_example.html rename to integrationExamples/gpt/hadronRtdProvider_example.html index 14debbd2698..065c8379956 100644 --- a/integrationExamples/gpt/haloRtdProvider_example.html +++ b/integrationExamples/gpt/hadronRtdProvider_example.html @@ -1,8 +1,8 @@ -Halo Id: -
+Hadron Id: +
-Halo Real-Time Data: +Hadron Real-Time Data:
diff --git a/integrationExamples/gpt/idImportLibrary_example.html b/integrationExamples/gpt/idImportLibrary_example.html index a3ef3f168c0..363e8015f53 100644 --- a/integrationExamples/gpt/idImportLibrary_example.html +++ b/integrationExamples/gpt/idImportLibrary_example.html @@ -47,23 +47,14 @@ params: { pid: '14', // Set your real identityLink placement ID here // notUse3P: true // true/false - If you do not want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will be fired. By default this propertt is undefined and 3p request will be fired.}, + }, storage: { type: 'html5', name: 'idl_env', expires: 30 } }, - { - name: "sharedId", - params: { - syncTime: 60 // in seconds, default is 24 hours - }, - storage: { - type: "html5", - name: "sharedid", - expires: 28 - } - } , { + { name: "liveIntentId", params: { publisherId: "9896876" @@ -78,10 +69,10 @@ name: "zeotapIdPlus" }, { - name: 'haloId', + name: 'hadronId', storage: { type: "html5", - name: "haloId", + name: "hadronId", expires: 28 } }, { diff --git a/integrationExamples/gpt/imRtdProvider_example.html b/integrationExamples/gpt/imRtdProvider_example.html new file mode 100644 index 00000000000..b98f053047b --- /dev/null +++ b/integrationExamples/gpt/imRtdProvider_example.html @@ -0,0 +1,115 @@ + + + + + + + + + + + + + +

IM RTD Prebid

+ +
+ +
+ +Intimate Merger Universal Identifier: +
+ +Intimate Merger Real-Time Data: +
+ + diff --git a/integrationExamples/gpt/ixMultiFormat.html b/integrationExamples/gpt/ixMultiFormat.html new file mode 100644 index 00000000000..c4ed5bb9b1e --- /dev/null +++ b/integrationExamples/gpt/ixMultiFormat.html @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + + diff --git a/integrationExamples/gpt/optimeraRtdProvider_example.html b/integrationExamples/gpt/optimeraRtdProvider_example.html new file mode 100644 index 00000000000..109a4c2b366 --- /dev/null +++ b/integrationExamples/gpt/optimeraRtdProvider_example.html @@ -0,0 +1,152 @@ + + + + + + + + + Optimera RTD Example + + + + + + + + + + +

Basic Prebid.js Example

+
Div-1
+
+ +
+ +
+ +
Div-0
+
+ +
+ + + + \ No newline at end of file diff --git a/integrationExamples/gpt/permutiveRtdProvider_example.html b/integrationExamples/gpt/permutiveRtdProvider_example.html index a06430bcdfa..b6a22096c90 100644 --- a/integrationExamples/gpt/permutiveRtdProvider_example.html +++ b/integrationExamples/gpt/permutiveRtdProvider_example.html @@ -159,6 +159,41 @@ ] } }); + pbjs.setBidderConfig({ + bidders: ['appnexus', 'rubicon'], + config: { + ortb2: { + site: { + name: "example", + domain: "page.example.com", + cat: ["IAB2"], + sectioncat: ["IAB2-2"], + pagecat: ["IAB2-2"], + page: "https://page.example.com/here.html", + ref: "https://ref.example.com", + keywords: "power tools, drills", + search: "drill", + content: {} + }, + user: { + yob: 1985, + gender: 'm', + keywords: 'a,b', + data: [ + { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ id: '687' }, { id: '123' }] + }, + { + name: 'permutive.com', + segment: [{ id: '1' }] + } + ] + } + } + } + }); pbjs.addAdUnits(adUnits); requestBids(); }); diff --git a/integrationExamples/gpt/revcontent_example_banner.html b/integrationExamples/gpt/revcontent_example_banner.html new file mode 100644 index 00000000000..cadd2e1a0b7 --- /dev/null +++ b/integrationExamples/gpt/revcontent_example_banner.html @@ -0,0 +1,116 @@ + + + + + Prebid.js Banner Example + + + + + + + + + + + +

Prebid.js Banner Example

+
+ +
+
+
+ +
+
+ diff --git a/integrationExamples/gpt/revcontent_example.html b/integrationExamples/gpt/revcontent_example_native.html similarity index 92% rename from integrationExamples/gpt/revcontent_example.html rename to integrationExamples/gpt/revcontent_example_native.html index d7a44df3014..07a06f3a25d 100644 --- a/integrationExamples/gpt/revcontent_example.html +++ b/integrationExamples/gpt/revcontent_example_native.html @@ -1,9 +1,9 @@ - - - + Prebid.js Native Example + +
diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 6fbdd69aea7..585bb1c97ae 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -72,10 +72,11 @@ "131": true, // id5Id "929": true, // parrableId "97": true, // identityLink - "887": true, // sharedId, uid2 + "887": true, // uid2 "95": true, // lotamePanoramaId "301": true, // zeotapIdPlus "91": true, // criteo + "737": true, // amxId } } } @@ -153,6 +154,7 @@ { "name": "merkleId", "params": { + "endpoint": "https://test_endpoint/", "vendor": "sdfg", "sv_cid": "dfg", "sv_pubid": "xcv", @@ -195,18 +197,6 @@ "expires": 30 } }, - { - "name": "sharedId", - // bidders: ["rubicon", "sampleBidders"], // to allow this ID for specific bidders - "params": { - "syncTime": 60 // in seconds, default is 24 hours - }, - "storage": { - "type": "cookie", - "name": "sharedid", - "expires": 28 - } - }, { "name": "lotamePanoramaId" }, @@ -225,10 +215,10 @@ "name": "zeotapIdPlus" }, { - "name": "haloId", + "name": "hadronId", "storage": { "type": "cookie", - "name": "haloId", + "name": "hadronId", "expires": 28 } }, @@ -238,6 +228,14 @@ { "name": "criteo" }, + { + "name": "amxId", + "storage": { + "type": "html5", + "name": "amxId", + "expires": 14 + } + }, { "name": "uid2" } @@ -248,7 +246,13 @@ // To get new token, register https://developer.chrome.com/origintrials/#/trials/active for Federated Learning of Cohorts token: "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9" } + }, + { + "name": "imuid", + "params": { + "cid": 5126 // Set your Intimate Merger Customer ID here for production } + } ], "syncDelay": 5000, "auctionDelay": 1000 diff --git a/integrationExamples/gpt/weboramaRtdProvider_example.html b/integrationExamples/gpt/weboramaRtdProvider_example.html new file mode 100644 index 00000000000..b81ec52b2c4 --- /dev/null +++ b/integrationExamples/gpt/weboramaRtdProvider_example.html @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + +
+

+ test webo ctx using prebid.js +

+
+

Basic Prebid.js Example

+
Div-1
+
+ +
+ + + + \ No newline at end of file diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index fce46bb380f..bea8b70b4fe 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -2,37 +2,45 @@ // this script can be returned by an ad server delivering a cross domain iframe, into which the // creative will be rendered, e.g. DFP delivering a SafeFrame -let windowLocation = window.location; -var urlParser = document.createElement('a'); +const windowLocation = window.location; +const urlParser = document.createElement('a'); urlParser.href = '%%PATTERN:url%%'; -var publisherDomain = urlParser.protocol + '//' + urlParser.hostname; +const publisherDomain = urlParser.protocol + '//' + urlParser.hostname; +const adId = '%%PATTERN:hb_adid%%'; + +function receiveMessage(ev) { + const origin = ev.origin || ev.originalEvent.origin; + if (origin === publisherDomain) { + renderAd(ev); + } +} function renderAd(ev) { - var key = ev.message ? 'message' : 'data'; - var adObject = {}; - try { - adObject = JSON.parse(ev[key]); - } catch (e) { - return; - } + const key = ev.message ? 'message' : 'data'; + let adObject = {}; + try { + adObject = JSON.parse(ev[key]); + } catch (e) { + return; + } - var origin = ev.origin || ev.originalEvent.origin; - if (adObject.message && adObject.message === 'Prebid Response' && - publisherDomain === origin && - adObject.adId === '%%PATTERN:hb_adid%%' && - (adObject.ad || adObject.adUrl)) { - var body = window.document.body; - var ad = adObject.ad; - var url = adObject.adUrl; - var width = adObject.width; - var height = adObject.height; + if (adObject.message && adObject.message === 'Prebid Response' && + adObject.adId === adId) { + try { + const body = window.document.body; + const ad = adObject.ad; + const url = adObject.adUrl; + const width = adObject.width; + const height = adObject.height; if (adObject.mediaType === 'video') { + signalRenderResult(false, { + reason: 'preventWritingOnMainDocument', + message: `Cannot render video ad ${adId}` + }); console.log('Error trying to write ad.'); - } else - - if (ad) { - var frame = document.createElement('iframe'); + } else if (ad) { + const frame = document.createElement('iframe'); frame.setAttribute('FRAMEBORDER', 0); frame.setAttribute('SCROLLING', 'no'); frame.setAttribute('MARGINHEIGHT', 0); @@ -46,24 +54,50 @@ frame.contentDocument.open(); frame.contentDocument.write(ad); frame.contentDocument.close(); + signalRenderResult(true); } else if (url) { body.insertAdjacentHTML('beforeend', ''); + signalRenderResult(true); } else { - console.log('Error trying to write ad. No ad for bid response id: ' + id); + signalRenderResult(false, { + reason: 'noAd', + message: `No ad for ${adId}` + }); + console.log(`Error trying to write ad. No ad markup or adUrl for ${adId}`); } + } catch (e) { + signalRenderResult(false, {reason: 'exception', message: e.message}); + console.log(`Error in rendering ad`, e); + } + } + + function signalRenderResult(success, {reason, message} = {}) { + const payload = { + message: 'Prebid Event', + adId, + event: success ? 'adRenderSucceeded' : 'adRenderFailed', } + if (!success) { + payload.info = {reason, message}; + } + window.parent.postMessage(JSON.stringify(payload), publisherDomain); } +} + + function requestAdFromPrebid() { - var message = JSON.stringify({ + const message = JSON.stringify({ message: 'Prebid Request', - adId: '%%PATTERN:hb_adid%%' + adId }); - window.parent.postMessage(message, publisherDomain); + const channel = new MessageChannel(); + channel.port1.onmessage = renderAd; + window.parent.postMessage(message, publisherDomain, [channel.port2]); } function listenAdFromPrebid() { - window.addEventListener('message', renderAd, false); + window.addEventListener('message', receiveMessage, false); } listenAdFromPrebid(); diff --git a/integrationExamples/noadserver/basic_noadserver.html b/integrationExamples/noadserver/basic_noadserver.html new file mode 100755 index 00000000000..ebc0e842775 --- /dev/null +++ b/integrationExamples/noadserver/basic_noadserver.html @@ -0,0 +1,106 @@ + + + + + + + + + + + + + +

Ad Serverless Test Page

+ +
+
+
+ + diff --git a/integrationExamples/postbid/bidViewabilityIO_example.html b/integrationExamples/postbid/bidViewabilityIO_example.html new file mode 100644 index 00000000000..2703013b29a --- /dev/null +++ b/integrationExamples/postbid/bidViewabilityIO_example.html @@ -0,0 +1,136 @@ + + + + + + + +
+ +
+ + + +
+ + + +
+ + + diff --git a/integrationExamples/postbid/postbid_prebidServer_example.html b/integrationExamples/postbid/postbid_prebidServer_example.html index 3af7b010872..9f4944884a0 100644 --- a/integrationExamples/postbid/postbid_prebidServer_example.html +++ b/integrationExamples/postbid/postbid_prebidServer_example.html @@ -14,11 +14,12 @@ })(); pbjs.que.push(function() { + var sizes = [[300, 250]]; var adUnits = [{ code: 'postbid_iframe', mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: sizes } }, bids: [ diff --git a/karma.conf.maker.js b/karma.conf.maker.js index 8af216d6262..be51947dae8 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -3,7 +3,7 @@ // For more information, see http://karma-runner.github.io/1.0/config/configuration-file.html var _ = require('lodash'); -var webpackConf = require('./webpack.conf'); +var webpackConf = require('./webpack.conf.js'); var karmaConstants = require('karma').constants; function newWebpackConfig(codeCoverage) { @@ -111,7 +111,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { var webpackConfig = newWebpackConfig(codeCoverage); var plugins = newPluginsArray(browserstack); - var files = file ? ['test/helpers/prebidGlobal.js', file] : ['test/test_index.js']; + var files = file ? ['test/test_deps.js', file] : ['test/test_index.js']; // This file opens the /debug.html tab automatically. // It has no real value unless you're running --watch, and intend to do some debugging in the browser. if (watchMode) { @@ -166,7 +166,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { browserNoActivityTimeout: 3e5, // default 10000 captureTimeout: 3e5, // default 60000, browserDisconnectTolerance: 3, - concurrency: 5, + concurrency: 6, plugins: plugins } diff --git a/modules/.submodules.json b/modules/.submodules.json index 7760d31cfff..2fb46377a64 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -1,34 +1,45 @@ { "userId": [ - "unifiedIdSystem", - "pubCommonIdSystem", - "id5IdSystem", - "parrableIdSystem", + "admixerIdSystem", + "adtelligentIdSystem", + "akamaiDAPIdSystem", + "amxIdSystem", "britepoolIdSystem", - "liveIntentIdSystem", - "lotamePanoramaId", - "merkleIdSystem", + "connectIdSystem", "criteoIdSystem", - "netIdSystem", + "deepintentDpesIdSystem", + "dmdIdSystem", + "fabrickIdSystem", + "flocIdSystem", + "hadronIdSystem", + "haloIdSystem", + "id5IdSystem", "identityLinkIdSystem", - "sharedIdSystem", + "idxIdSystem", + "imuIdSystem", "intentIqIdSystem", - "zeotapIdPlusIdSystem", - "haloIdSystem", - "quantcastIdSystem", - "deepintentDpesIdSystem", + "justIdSystem", + "kinessoIdSystem", + "liveIntentIdSystem", + "lotamePanoramaIdSystem", + "merkleIdSystem", + "mwOpenLinkIdSystem", + "naveggIdSystem", + "netIdSystem", "nextrollIdSystem", - "idxIdSystem", - "fabrickIdSystem", - "verizonMediaIdSystem", + "novatiqIdSystem", + "parrableIdSystem", "pubProvidedIdSystem", - "mwOpenLinkIdSystem", + "publinkIdSystem", + "quantcastIdSystem", + "sharedIdSystem", "tapadIdSystem", - "novatiqIdSystem", + "trustpidSystem", "uid2IdSystem", - "admixerIdSystem", - "dmdIdSystem", - "flocIdSystem" + "unifiedIdSystem", + "verizonMediaIdSystem", + "zeotapIdPlusIdSystem", + "adqueryIdSystem" ], "adpod": [ "freeWheelAdserverVideo", @@ -36,10 +47,22 @@ ], "rtdModule": [ "browsiRtdProvider", + "dgkeywordRtdProvider", + "geoedgeRtdProvider", + "hadronRtdProvider", "haloRtdProvider", + "iasRtdProvider", "jwplayerRtdProvider", + "medianetRtdProvider", + "optimeraRtdProvider", + "permutiveRtdProvider", "reconciliationRtdProvider", - "geoedgeRtdProvider", - "sirdataRtdProvider" + "sirdataRtdProvider", + "timeoutRtdProvider", + "weboramaRtdProvider" + ], + "fpdModule": [ + "enrichmentFpdModule", + "validationFpdModule" ] } diff --git a/modules/1ad4goodBidAdapter.js b/modules/1ad4goodBidAdapter.js deleted file mode 100644 index 178f3765587..00000000000 --- a/modules/1ad4goodBidAdapter.js +++ /dev/null @@ -1,399 +0,0 @@ -import * as utils 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 includes from 'core-js-pure/features/array/includes.js'; - -const BIDDER_CODE = '1ad4good'; -const URL = 'https://hb.1ad4good.org/prebid'; -const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', - 'startdelay', 'skippable', 'playback_method', 'frameworks']; -const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; -const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately -const SOURCE = 'pbjs'; -const MAX_IMPS_PER_REQUEST = 15; - -export const spec = { - code: BIDDER_CODE, - aliases: ['adsforgood', 'ads4good', '1adsforgood'], - supportedMediaTypes: [BANNER, VIDEO], - - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - return !!(bid.params.placementId); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(bidRequests, bidderRequest) { - const tags = bidRequests.map(bidToTag); - const userObjBid = find(bidRequests, hasUserInfo); - let userObj; - if (userObjBid) { - userObj = {}; - Object.keys(userObjBid.params.user) - .filter(param => includes(USER_PARAMS, param)) - .forEach(param => userObj[param] = userObjBid.params.user[param]); - } - - const appDeviceObjBid = find(bidRequests, hasAppDeviceInfo); - let appDeviceObj; - if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app) { - appDeviceObj = {}; - Object.keys(appDeviceObjBid.params.app) - .filter(param => includes(APP_DEVICE_PARAMS, param)) - .forEach(param => appDeviceObj[param] = appDeviceObjBid.params.app[param]); - } - - const appIdObjBid = find(bidRequests, hasAppId); - let appIdObj; - if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { - appIdObj = { - appid: appIdObjBid.params.app.id - }; - } - - const payload = { - tags: [...tags], - user: userObj, - sdk: { - source: SOURCE, - version: '$prebid.version$' - } - }; - - if (appDeviceObjBid) { - payload.device = appDeviceObj - } - if (appIdObjBid) { - payload.app = appIdObj; - } - - if (bidderRequest && bidderRequest.gdprConsent) { - // note - objects for impbus use underscore instead of camelCase - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - } - - if (bidderRequest && bidderRequest.refererInfo) { - let refererinfo = { - rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), - rd_top: bidderRequest.refererInfo.reachedTop, - rd_ifs: bidderRequest.refererInfo.numIframes, - rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') - } - payload.referrer_detection = refererinfo; - } - - const request = formatRequest(payload, bidderRequest); - return request; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, {bidderRequest}) { - serverResponse = serverResponse.body; - const bids = []; - if (!serverResponse || serverResponse.error) { - let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; - if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } - utils.logError(errorMessage); - return bids; - } - - if (serverResponse.tags) { - serverResponse.tags.forEach(serverBid => { - const rtbBid = getRtbBid(serverBid); - if (rtbBid) { - if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { - const bid = newBid(serverBid, rtbBid, bidderRequest); - bid.mediaType = parseMediaType(rtbBid); - bids.push(bid); - } - } - }); - } - - return bids; - }, - - transformBidParams: function(params, isOpenRtb) { - params = utils.convertTypes({ - 'placementId': 'number', - 'keywords': utils.transformBidderParamKeywords - }, params); - - if (isOpenRtb) { - params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; - if (params.usePaymentRule) { delete params.usePaymentRule; } - - if (isPopulatedArray(params.keywords)) { - params.keywords.forEach(deleteValues); - } - - Object.keys(params).forEach(paramKey => { - let convertedKey = utils.convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - - return params; - }, - - /** - * Add element selector to javascript tracker to improve native viewability - * @param {Bid} bid - */ - onBidWon: function(bid) { - } -} - -function isPopulatedArray(arr) { - return !!(utils.isArray(arr) && arr.length > 0); -} - -function deleteValues(keyPairObj) { - if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { - delete keyPairObj.value; - } -} - -function formatRequest(payload, bidderRequest) { - let request = []; - - if (payload.tags.length > MAX_IMPS_PER_REQUEST) { - const clonedPayload = utils.deepClone(payload); - - utils.chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { - clonedPayload.tags = tags; - const payloadString = JSON.stringify(clonedPayload); - request.push({ - method: 'POST', - url: URL, - data: payloadString, - bidderRequest - }); - }); - } else { - const payloadString = JSON.stringify(payload); - request = { - method: 'POST', - url: URL, - data: payloadString, - bidderRequest - }; - } - - return request; -} - -/** - * Unpack the Server's Bid into a Prebid-compatible one. - * @param serverBid - * @param rtbBid - * @param bidderRequest - * @return Bid - */ -function newBid(serverBid, rtbBid, bidderRequest) { - const bidRequest = utils.getBidRequest(serverBid.uuid, [bidderRequest]); - const bid = { - requestId: serverBid.uuid, - cpm: rtbBid.cpm, - creativeId: rtbBid.creative_id, - dealId: rtbBid.deal_id, - currency: 'USD', - netRevenue: true, - ttl: 300, - adUnitCode: bidRequest.adUnitCode, - ads4good: { - buyerMemberId: rtbBid.buyer_member_id, - dealPriority: rtbBid.deal_priority, - dealCode: rtbBid.deal_code - } - }; - - if (rtbBid.advertiser_id) { - bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); - } - - if (rtbBid.rtb.video) { - Object.assign(bid, { - width: rtbBid.rtb.video.player_width, - height: rtbBid.rtb.video.player_height, - vastUrl: rtbBid.rtb.video.asset_url, - vastImpUrl: rtbBid.notify_url, - ttl: 3600 - }); - } else { - Object.assign(bid, { - width: rtbBid.rtb.banner.width, - height: rtbBid.rtb.banner.height, - ad: rtbBid.rtb.banner.content - }); - try { - const url = rtbBid.rtb.trackers[0].impression_urls[0]; - const tracker = utils.createTrackPixelHtml(url); - bid.ad += tracker; - } catch (error) { - utils.logError('Error appending tracking pixel', error); - } - } - - return bid; -} - -function bidToTag(bid) { - const tag = {}; - tag.sizes = transformSizes(bid.sizes); - tag.primary_size = tag.sizes[0]; - tag.ad_types = []; - tag.uuid = bid.bidId; - if (bid.params.placementId) { - tag.id = parseInt(bid.params.placementId, 10); - } - if (bid.params.cpm) { - tag.cpm = bid.params.cpm; - } - tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false - tag.prebid = true; - tag.disable_psa = true; - if (bid.params.reserve) { - tag.reserve = bid.params.reserve; - } - if (bid.params.position) { - tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; - } - if (bid.params.trafficSourceCode) { - tag.traffic_source_code = bid.params.trafficSourceCode; - } - if (bid.params.privateSizes) { - tag.private_sizes = transformSizes(bid.params.privateSizes); - } - if (bid.params.supplyType) { - tag.supply_type = bid.params.supplyType; - } - if (bid.params.pubClick) { - tag.pubclick = bid.params.pubClick; - } - if (bid.params.extInvCode) { - tag.ext_inv_code = bid.params.extInvCode; - } - if (bid.params.externalImpId) { - tag.external_imp_id = bid.params.externalImpId; - } - if (!utils.isEmpty(bid.params.keywords)) { - let keywords = utils.transformBidderParamKeywords(bid.params.keywords); - - if (keywords.length > 0) { - keywords.forEach(deleteValues); - } - tag.keywords = keywords; - } - - const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - - if (bid.mediaType === VIDEO || videoMediaType) { - tag.ad_types.push(VIDEO); - } - - // instream gets vastUrl, outstream gets vastXml - if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { - tag.require_asset_url = true; - } - - if (bid.params.video) { - tag.video = {}; - // place any valid video params on the tag - Object.keys(bid.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => tag.video[param] = bid.params.video[param]); - } - - if (bid.renderer) { - tag.video = Object.assign({}, tag.video, {custom_renderer_present: true}); - } - - if ( - (utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) || - (bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER])) - ) { - tag.ad_types.push(BANNER); - } - - return tag; -} - -/* Turn bid request sizes into ut-compatible format */ -function transformSizes(requestSizes) { - let sizes = []; - let sizeObj = {}; - - if (utils.isArray(requestSizes) && requestSizes.length === 2 && - !utils.isArray(requestSizes[0])) { - sizeObj.width = parseInt(requestSizes[0], 10); - sizeObj.height = parseInt(requestSizes[1], 10); - sizes.push(sizeObj); - } else if (typeof requestSizes === 'object') { - for (let i = 0; i < requestSizes.length; i++) { - let size = requestSizes[i]; - sizeObj = {}; - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - sizes.push(sizeObj); - } - } - - return sizes; -} - -function hasUserInfo(bid) { - return !!bid.params.user; -} - -function hasAppDeviceInfo(bid) { - if (bid.params) { - return !!bid.params.app - } -} - -function hasAppId(bid) { - if (bid.params && bid.params.app) { - return !!bid.params.app.id - } - return !!bid.params.app -} - -function getRtbBid(tag) { - return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); -} - -function parseMediaType(rtbBid) { - const adType = rtbBid.ad_type; - if (adType === VIDEO) { - return VIDEO; - } else { - return BANNER; - } -} - -registerBidder(spec); diff --git a/modules/1ad4goodBidAdapter.md b/modules/1ad4goodBidAdapter.md deleted file mode 100644 index 1e9dfe5e04c..00000000000 --- a/modules/1ad4goodBidAdapter.md +++ /dev/null @@ -1,87 +0,0 @@ -# Overview - -``` -Module Name: 1ad4good Bid Adapter -Module Type: Bidder Adapter -Maintainer: info@1ad4good.org -``` - -# Description - -Connects to 1ad4good exchange for bids. - -1ad4good bid adapter supports Banner and Video (instream). - -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [{ - bidder: '1ad4good', - params: { - placementId: 13144370 - } - }] - }, - // Video instream adUnit - { - code: 'video-instream', - sizes: [[640, 480]], - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'instream' - }, - }, - bids: [{ - bidder: '1ad4good', - params: { - placementId: 13232361, - video: { - skippable: true, - playback_methods: ['auto_play_sound_off'] - } - } - }] - }, - - // Banner adUnit in a App Webview - // Only use this for situations where prebid.js is in a webview of an App - // See Prebid Mobile for displaying ads via an SDK - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - } - bids: [{ - bidder: '1ad4good', - params: { - placementId: 13144370, - app: { - id: "B1O2W3M4AN.com.prebid.webview", - geo: { - lat: 40.0964439, - lng: -75.3009142 - }, - device_id: { - idfa: "4D12078D-3246-4DA4-AD5E-7610481E7AE", // Apple advertising identifier - aaid: "38400000-8cf0-11bd-b23e-10b96e40000d", // Android advertising identifier - md5udid: "5756ae9022b2ea1e47d84fead75220c8", // MD5 hash of the ANDROID_ID - sha1udid: "4DFAA92388699AC6539885AEF1719293879985BF", // SHA1 hash of the ANDROID_ID - windowsadid: "750c6be243f1c4b5c9912b95a5742fc5" // Windows advertising identifier - } - } - } - }] - } -]; -``` diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 593ec684f69..6b6ff7da99e 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -1,8 +1,20 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; -import * as utils from '../src/utils.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; - +import { + deepAccess, + uniques, + isArray, + getWindowTop, + isGptPubadsDefined, + isSlotMatchingAdUnitCode, + logInfo, + logWarn, + getWindowSelf, + mergeDeep, +} from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +// **************************** UTILS *************************** // const BIDDER_CODE = '33across'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; const SYNC_ENDPOINT = 'https://ssc-cms.33across.com/ps/?m=xch&rt=html&ru=deb'; @@ -40,6 +52,14 @@ const adapterState = { const NON_MEASURABLE = 'nm'; +function getTTXConfig() { + const ttxSettings = Object.assign({}, + config.getConfig('ttxSettings') + ); + + return ttxSettings; +} + // **************************** VALIDATION *************************** // function isBidRequestValid(bid) { return ( @@ -62,7 +82,7 @@ function _validateBasic(bid) { } function _validateGUID(bid) { - const siteID = utils.deepAccess(bid, 'params.siteId', '') || ''; + const siteID = deepAccess(bid, 'params.siteId', '') || ''; if (siteID.trim().match(GUID_PATTERN) === null) { return false; } @@ -71,7 +91,8 @@ function _validateGUID(bid) { } function _validateBanner(bid) { - const banner = utils.deepAccess(bid, 'mediaTypes.banner'); + const banner = deepAccess(bid, 'mediaTypes.banner'); + // If there's no banner no need to validate against banner rules if (banner === undefined) { return true; @@ -85,8 +106,8 @@ function _validateBanner(bid) { } function _validateVideo(bid) { - const videoAdUnit = utils.deepAccess(bid, 'mediaTypes.video'); - const videoBidderParams = utils.deepAccess(bid, 'params.video', {}); + const videoAdUnit = deepAccess(bid, 'mediaTypes.video'); + const videoBidderParams = deepAccess(bid, 'params.video', {}); // If there's no video no need to validate against video rules if (videoAdUnit === undefined) { @@ -138,91 +159,125 @@ function _validateVideo(bid) { // NOTE: With regards to gdrp consent data, the server will independently // infer the gdpr applicability therefore, setting the default value to false function buildRequests(bidRequests, bidderRequest) { + const { + ttxSettings, + gdprConsent, + uspConsent, + pageUrl + } = _buildRequestParams(bidRequests, bidderRequest); + + const groupedRequests = _buildRequestGroups(ttxSettings, bidRequests); + + const serverRequests = []; + + for (const key in groupedRequests) { + serverRequests.push( + _createServerRequest({ + bidRequests: groupedRequests[key], + gdprConsent, + uspConsent, + pageUrl, + ttxSettings + }) + ) + } + + return serverRequests; +} + +function _buildRequestParams(bidRequests, bidderRequest) { + const ttxSettings = getTTXConfig(); + const gdprConsent = Object.assign({ consentString: undefined, gdprApplies: false }, bidderRequest && bidderRequest.gdprConsent); const uspConsent = bidderRequest && bidderRequest.uspConsent; + const pageUrl = (bidderRequest && bidderRequest.refererInfo) ? (bidderRequest.refererInfo.referer) : (undefined); - adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(utils.uniques); + adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques); - return bidRequests.map(bidRequest => _createServerRequest( - { - bidRequest, - gdprConsent, - uspConsent, - pageUrl - }) - ); + return { + ttxSettings, + gdprConsent, + uspConsent, + pageUrl + } +} + +function _buildRequestGroups(ttxSettings, bidRequests) { + const bidRequestsComplete = bidRequests.map(_inferProduct); + const enableSRAMode = ttxSettings && ttxSettings.enableSRAMode; + const keyFunc = (enableSRAMode === true) ? _getSRAKey : _getMRAKey; + + return _groupBidRequests(bidRequestsComplete, keyFunc); +} + +function _groupBidRequests(bidRequests, keyFunc) { + const groupedRequests = {}; + + bidRequests.forEach((req) => { + const key = keyFunc(req); + + groupedRequests[key] = groupedRequests[key] || []; + groupedRequests[key].push(req); + }); + + return groupedRequests; +} + +function _getSRAKey(bidRequest) { + return `${bidRequest.params.siteId}:${bidRequest.params.productId}`; +} + +function _getMRAKey(bidRequest) { + return `${bidRequest.bidId}`; } // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request -// NOTE: At this point, TTX only accepts request for a single impression -function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl}) { +function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, ttxSettings }) { const ttxRequest = {}; - const params = bidRequest.params; + const { siteId, test } = bidRequests[0].params; /* * Infer data for the request payload */ - ttxRequest.imp = [{}]; + ttxRequest.imp = []; - if (utils.deepAccess(bidRequest, 'mediaTypes.banner')) { - ttxRequest.imp[0].banner = { - ..._buildBannerORTB(bidRequest) - } - } - - if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { - ttxRequest.imp[0].video = _buildVideoORTB(bidRequest); - } - - ttxRequest.imp[0].ext = { - ttx: { - prod: _getProduct(bidRequest) - } - }; + bidRequests.forEach((req) => { + ttxRequest.imp.push(_buildImpORTB(req)); + }); - ttxRequest.site = { id: params.siteId }; + ttxRequest.site = { id: siteId }; if (pageUrl) { ttxRequest.site.page = pageUrl; } - // Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and - // therefore in ad targetting process - ttxRequest.id = bidRequest.bidId; + ttxRequest.id = bidRequests[0].auctionId; if (gdprConsent.consentString) { - ttxRequest.user = setExtension( - ttxRequest.user, - 'consent', - gdprConsent.consentString - ) + ttxRequest.user = setExtensions(ttxRequest.user, { + 'consent': gdprConsent.consentString + }); } - if (Array.isArray(bidRequest.userIdAsEids) && bidRequest.userIdAsEids.length > 0) { - ttxRequest.user = setExtension( - ttxRequest.user, - 'eids', - bidRequest.userIdAsEids - ) + if (Array.isArray(bidRequests[0].userIdAsEids) && bidRequests[0].userIdAsEids.length > 0) { + ttxRequest.user = setExtensions(ttxRequest.user, { + 'eids': bidRequests[0].userIdAsEids + }); } - ttxRequest.regs = setExtension( - ttxRequest.regs, - 'gdpr', - Number(gdprConsent.gdprApplies) - ); + ttxRequest.regs = setExtensions(ttxRequest.regs, { + 'gdpr': Number(gdprConsent.gdprApplies) + }); if (uspConsent) { - ttxRequest.regs = setExtension( - ttxRequest.regs, - 'us_privacy', - uspConsent - ) + ttxRequest.regs = setExtensions(ttxRequest.regs, { + 'us_privacy': uspConsent + }); } ttxRequest.ext = { @@ -235,16 +290,14 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl } }; - if (bidRequest.schain) { - ttxRequest.source = setExtension( - ttxRequest.source, - 'schain', - bidRequest.schain - ) + if (bidRequests[0].schain) { + ttxRequest.source = setExtensions(ttxRequest.source, { + 'schain': bidRequests[0].schain + }); } // Finally, set the openRTB 'test' param if this is to be a test bid - if (params.test === 1) { + if (test === 1) { ttxRequest.test = 1; } @@ -257,8 +310,7 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl }; // Allow the ability to configure the HB endpoint for testing purposes. - const ttxSettings = config.getConfig('ttxSettings'); - const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${params.siteId}`; + const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${siteId}`; // Return the server request return { @@ -270,17 +322,39 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl } // BUILD REQUESTS: SET EXTENSIONS -function setExtension(obj = {}, key, value) { - return Object.assign({}, obj, { - ext: Object.assign({}, obj.ext, { - [key]: value - }) +function setExtensions(obj = {}, extFields) { + return mergeDeep({}, obj, { + 'ext': extFields }); } +// BUILD REQUESTS: IMP +function _buildImpORTB(bidRequest) { + const imp = { + id: bidRequest.bidId, + ext: { + ttx: { + prod: deepAccess(bidRequest, 'params.productId') + } + } + }; + + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + imp.banner = { + ..._buildBannerORTB(bidRequest) + } + } + + if (deepAccess(bidRequest, 'mediaTypes.video')) { + imp.video = _buildVideoORTB(bidRequest); + } + + return imp; +} + // BUILD REQUESTS: SIZE INFERENCE function _transformSizes(sizes) { - if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) { + if (isArray(sizes) && sizes.length === 2 && !isArray(sizes[0])) { return [ _getSize(sizes) ]; } @@ -295,6 +369,14 @@ function _getSize(size) { } // BUILD REQUESTS: PRODUCT INFERENCE +function _inferProduct(bidRequest) { + return mergeDeep({}, bidRequest, { + params: { + productId: _getProduct(bidRequest) + } + }); +} + function _getProduct(bidRequest) { const { params, mediaTypes } = bidRequest; @@ -309,7 +391,7 @@ function _getProduct(bidRequest) { // BUILD REQUESTS: BANNER function _buildBannerORTB(bidRequest) { - const bannerAdUnit = utils.deepAccess(bidRequest, 'mediaTypes.banner', {}); + const bannerAdUnit = deepAccess(bidRequest, 'mediaTypes.banner', {}); const element = _getAdSlotHTMLElement(bidRequest.adUnitCode); const sizes = _transformSizes(bannerAdUnit.sizes); @@ -341,7 +423,7 @@ function _buildBannerORTB(bidRequest) { const minSize = _getMinSize(sizes); const viewabilityAmount = _isViewabilityMeasurable(element) - ? _getViewability(element, utils.getWindowTop(), minSize) + ? _getViewability(element, getWindowTop(), minSize) : NON_MEASURABLE; const ext = contributeViewability(viewabilityAmount); @@ -355,8 +437,8 @@ function _buildBannerORTB(bidRequest) { // BUILD REQUESTS: VIDEO // eslint-disable-next-line no-unused-vars function _buildVideoORTB(bidRequest) { - const videoAdUnit = utils.deepAccess(bidRequest, 'mediaTypes.video', {}); - const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {}); + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); const videoParams = { ...videoAdUnit, @@ -365,7 +447,7 @@ function _buildVideoORTB(bidRequest) { const video = {} - const {w, h} = _getSize(videoParams.playerSize[0]); + const { w, h } = _getSize(videoParams.playerSize[0]); video.w = w; video.h = h; @@ -386,11 +468,11 @@ function _buildVideoORTB(bidRequest) { if (product === PRODUCT.INSTREAM) { video.startdelay = video.startdelay || 0; video.placement = 1; - }; + } // bidfloors if (typeof bidRequest.getFloor === 'function') { - const bidfloors = _getBidFloors(bidRequest, {w: video.w, h: video.h}, VIDEO); + const bidfloors = _getBidFloors(bidRequest, { w: video.w, h: video.h }, VIDEO); if (bidfloors) { Object.assign(video, { @@ -402,6 +484,7 @@ function _buildVideoORTB(bidRequest) { }); } } + return video; } @@ -430,23 +513,23 @@ function _getViewability(element, topWin, { w, h } = {}) { } function _mapAdUnitPathToElementId(adUnitCode) { - if (utils.isGptPubadsDefined()) { + if (isGptPubadsDefined()) { // eslint-disable-next-line no-undef const adSlots = googletag.pubads().getSlots(); - const isMatchingAdSlot = utils.isSlotMatchingAdUnitCode(adUnitCode); + const isMatchingAdSlot = isSlotMatchingAdUnitCode(adUnitCode); for (let i = 0; i < adSlots.length; i++) { if (isMatchingAdSlot(adSlots[i])) { const id = adSlots[i].getSlotElementId(); - utils.logInfo(`[33Across Adapter] Map ad unit path to HTML element id: '${adUnitCode}' -> ${id}`); + logInfo(`[33Across Adapter] Map ad unit path to HTML element id: '${adUnitCode}' -> ${id}`); return id; } } } - utils.logWarn(`[33Across Adapter] Unable to locate element for ad unit code: '${adUnitCode}'`); + logWarn(`[33Across Adapter] Unable to locate element for ad unit code: '${adUnitCode}'`); return null; } @@ -547,61 +630,68 @@ function contributeViewability(viewabilityAmount) { function _isIframe() { try { - return utils.getWindowSelf() !== utils.getWindowTop(); + return getWindowSelf() !== getWindowTop(); } catch (e) { return true; } } // **************************** INTERPRET RESPONSE ******************************** // -// NOTE: At this point, the response from 33exchange will only ever contain one bid -// i.e. the highest bid function interpretResponse(serverResponse, bidRequest) { - const bidResponses = []; - - // If there are bids, look at the first bid of the first seatbid (see NOTE above for assumption about ttx) - if (serverResponse.body.seatbid.length > 0 && serverResponse.body.seatbid[0].bid.length > 0) { - bidResponses.push(_createBidResponse(serverResponse.body)); - } - - return bidResponses; + const { seatbid, cur = 'USD' } = serverResponse.body; + + if (!isArray(seatbid)) { + return []; + } + + // Pick seats with valid bids and convert them into an Array of responses + // in format expected by Prebid Core + return seatbid + .filter((seat) => ( + isArray(seat.bid) && + seat.bid.length > 0 + )) + .reduce((acc, seat) => { + return acc.concat( + seat.bid.map((bid) => _createBidResponse(bid, cur)) + ); + }, []); } -// All this assumes that only one bid is ever returned by ttx -function _createBidResponse(response) { +function _createBidResponse(bid, cur) { const isADomainPresent = - response.seatbid[0].bid[0].adomain && response.seatbid[0].bid[0].adomain.length; - const bid = { - requestId: response.id, + bid.adomain && bid.adomain.length; + const bidResponse = { + requestId: bid.impid, bidderCode: BIDDER_CODE, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ad: response.seatbid[0].bid[0].adm, - ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].crid, - mediaType: utils.deepAccess(response.seatbid[0].bid[0], 'ext.ttx.mediaType', BANNER), - currency: response.cur, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: bid.ttl || 60, + creativeId: bid.crid, + mediaType: deepAccess(bid, 'ext.ttx.mediaType', BANNER), + currency: cur, netRevenue: true } if (isADomainPresent) { - bid.meta = { - advertiserDomains: response.seatbid[0].bid[0].adomain + bidResponse.meta = { + advertiserDomains: bid.adomain }; } - if (bid.mediaType === VIDEO) { - const vastType = utils.deepAccess(response.seatbid[0].bid[0], 'ext.ttx.vastType', 'xml'); + if (bidResponse.mediaType === VIDEO) { + const vastType = deepAccess(bid, 'ext.ttx.vastType', 'xml'); if (vastType === 'xml') { - bid.vastXml = bid.ad; + bidResponse.vastXml = bidResponse.ad; } else { - bid.vastUrl = bid.ad; + bidResponse.vastUrl = bidResponse.ad; } } - return bid; + return bidResponse; } // **************************** USER SYNC *************************** // diff --git a/modules/7xbidBidAdapter.js b/modules/7xbidBidAdapter.js deleted file mode 100644 index b925912a399..00000000000 --- a/modules/7xbidBidAdapter.js +++ /dev/null @@ -1,159 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = '7xbid'; -const BIDDER_ALIAS = '7xb'; -const ENDPOINT_BANNER = '//bidder.7xbid.com/api/v1/prebid/banner'; -const ENDPOINT_NATIVE = '//bidder.7xbid.com/api/v1/prebid/native'; -const COOKIE_SYNC_URL = '//bidder.7xbid.com/api/v1/cookie/gen'; -const SUPPORTED_MEDIA_TYPES = ['banner', 'native']; -const SUPPORTED_CURRENCIES = ['USD', 'JPY']; -const DEFAULT_CURRENCY = 'JPY'; -const NET_REVENUE = true; - -/** - * updated to support prebid 3.0 - remove utils.getTopWindowUrl() - */ - -const _encodeURIComponent = function(a) { - let b = window.encodeURIComponent(a); - b = b.replace(/'/g, '%27'); - return b; -} - -export const _getUrlVars = function(url) { - var hash; - var myJson = {}; - var hashes = url.slice(url.indexOf('?') + 1).split('&'); - for (var i = 0; i < hashes.length; i++) { - hash = hashes[i].split('='); - myJson[hash[0]] = hash[1]; - } - return myJson; -} - -export const spec = { - code: BIDDER_CODE, - aliases: [BIDDER_ALIAS], // short code - supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - isBidRequestValid: function(bid) { - if (!(bid.params.placementId)) { - return false; - } - - if (bid.params.hasOwnProperty('currency') && - SUPPORTED_CURRENCIES.indexOf(bid.params.currency) === -1) { - utils.logInfo('Invalid currency type, we support only JPY and USD!') - return false; - } - - return true; - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests, bidderRequest) { - let serverRequests = []; - var refererInfo; - if (bidderRequest && bidderRequest.refererInfo) { - refererInfo = bidderRequest.refererInfo; - } - validBidRequests.forEach((bid, i) => { - let endpoint = ENDPOINT_BANNER - let data = { - 'placementid': bid.params.placementId, - 'cur': bid.params.hasOwnProperty('currency') ? bid.params.currency : DEFAULT_CURRENCY, - 'ua': navigator.userAgent, - 'loc': utils.deepAccess(bidderRequest, 'refererInfo.referer'), - 'topframe': (window.parent === window.self) ? 1 : 0, - 'sw': screen && screen.width, - 'sh': screen && screen.height, - 'cb': Math.floor(Math.random() * 99999999999), - 'tpaf': 1, - 'cks': 1, - 'requestid': bid.bidId - }; - - if (bid.hasOwnProperty('nativeParams')) { - endpoint = ENDPOINT_NATIVE - data.tkf = 1 // return url tracker - data.ad_track = '1' - data.apiv = '1.1.0' - } - - if (refererInfo && refererInfo.referer) { - data.referer = refererInfo.referer; - } else { - data.referer = ''; - } - - serverRequests.push({ - method: 'GET', - url: endpoint, - data: utils.parseQueryStringParameters(data) - }) - }) - - return serverRequests; - }, - interpretResponse: function(serverResponse, request) { - const data = _getUrlVars(request.data) - const successBid = serverResponse.body || {}; - let bidResponses = []; - if (successBid.hasOwnProperty(data.placementid)) { - let bid = successBid[data.placementid] - let bidResponse = { - requestId: bid.requestid, - cpm: bid.price, - creativeId: bid.creativeId, - currency: bid.cur, - netRevenue: NET_REVENUE, - ttl: 700 - }; - - if (bid.hasOwnProperty('title')) { // it is native ad response - bidResponse.mediaType = 'native' - bidResponse.native = { - title: bid.title, - body: bid.description, - cta: bid.cta, - sponsoredBy: bid.advertiser, - clickUrl: _encodeURIComponent(bid.landingURL), - impressionTrackers: bid.trackings, - } - if (bid.screenshots) { - bidResponse.native.image = { - url: bid.screenshots.url, - height: bid.screenshots.height, - width: bid.screenshots.width, - } - } - if (bid.icon) { - bidResponse.native.icon = { - url: bid.icon.url, - height: bid.icon.height, - width: bid.icon.width, - } - } - } else { - bidResponse.ad = bid.adm - bidResponse.width = bid.width - bidResponse.height = bid.height - } - - bidResponses.push(bidResponse); - } - - return bidResponses; - }, - getUserSyncs: function(syncOptions, serverResponses) { - return [{ - type: 'image', - url: COOKIE_SYNC_URL - }]; - } -} -registerBidder(spec); diff --git a/modules/a4gBidAdapter.js b/modules/a4gBidAdapter.js index 1961dba1f10..03f9d6fd726 100644 --- a/modules/a4gBidAdapter.js +++ b/modules/a4gBidAdapter.js @@ -1,5 +1,5 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; +import { _each } from '../src/utils.js'; const A4G_BIDDER_CODE = 'a4g'; const A4G_CURRENCY = 'USD'; @@ -28,7 +28,7 @@ export const spec = { const sizeParams = []; const zoneIds = []; - utils._each(validBidRequests, function(bid) { + _each(validBidRequests, function(bid) { if (!deliveryUrl && typeof bid.params.deliveryUrl === 'string') { deliveryUrl = bid.params.deliveryUrl; } @@ -66,7 +66,7 @@ export const spec = { interpretResponse: function(serverResponses, request) { const bidResponses = []; - utils._each(serverResponses.body, function(response) { + _each(serverResponses.body, function(response) { if (response.cpm > 0) { const bidResponse = { requestId: response.id, @@ -77,7 +77,10 @@ export const spec = { currency: A4G_CURRENCY, netRevenue: true, ttl: A4G_TTL, - ad: response.ad + ad: response.ad, + meta: { + advertiserDomains: response.adomain && response.adomain.length > 0 ? response.adomain : [] + } }; bidResponses.push(bidResponse); } diff --git a/modules/aardvarkBidAdapter.js b/modules/aardvarkBidAdapter.js deleted file mode 100644 index 0b864286868..00000000000 --- a/modules/aardvarkBidAdapter.js +++ /dev/null @@ -1,262 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'aardvark'; -const DEFAULT_ENDPOINT = 'bidder.rtk.io'; -const SYNC_ENDPOINT = 'sync.rtk.io'; -const AARDVARK_TTL = 300; -const AARDVARK_CURRENCY = 'USD'; - -let hasSynced = false; - -export function resetUserSync() { - hasSynced = false; -} - -export const spec = { - code: BIDDER_CODE, - gvlid: 52, - aliases: ['adsparc', 'safereach'], - - isBidRequestValid: function(bid) { - return ((typeof bid.params.ai === 'string') && !!bid.params.ai.length && - (typeof bid.params.sc === 'string') && !!bid.params.sc.length); - }, - - buildRequests: function(validBidRequests, bidderRequest) { - var auctionCodes = []; - var requests = []; - var requestsMap = {}; - var referer = bidderRequest.refererInfo.referer; - var pageCategories = []; - var tdId = ''; - var width = window.innerWidth; - var height = window.innerHeight; - var schain = ''; - - // This reference to window.top can cause issues when loaded in an iframe if not protected with a try/catch. - try { - var topWin = utils.getWindowTop(); - if (topWin.rtkcategories && Array.isArray(topWin.rtkcategories)) { - pageCategories = topWin.rtkcategories; - } - width = topWin.innerWidth; - height = topWin.innerHeight; - } catch (e) {} - - if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.tdid'))) { - tdId = validBidRequests[0].userId.tdid; - } - - schain = spec.serializeSupplyChain(utils.deepAccess(validBidRequests, '0.schain')); - - utils._each(validBidRequests, function(b) { - var rMap = requestsMap[b.params.ai]; - if (!rMap) { - rMap = { - shortCodes: [], - payload: { - version: 1, - jsonp: false, - rtkreferer: referer, - w: width, - h: height - }, - endpoint: DEFAULT_ENDPOINT - }; - - if (tdId) { - rMap.payload.tdid = tdId; - } - if (schain) { - rMap.payload.schain = schain; - } - - if (pageCategories && pageCategories.length) { - rMap.payload.categories = pageCategories.slice(0); - } - - if (b.params.categories && b.params.categories.length) { - rMap.payload.categories = rMap.payload.categories || [] - utils._each(b.params.categories, function(cat) { - rMap.payload.categories.push(cat); - }); - } - - if (bidderRequest.gdprConsent) { - rMap.payload.gdpr = false; - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { - rMap.payload.gdpr = bidderRequest.gdprConsent.gdprApplies; - } - if (rMap.payload.gdpr) { - rMap.payload.consent = bidderRequest.gdprConsent.consentString; - } - } - - requestsMap[b.params.ai] = rMap; - auctionCodes.push(b.params.ai); - } - - if (bidderRequest.uspConsent) { - rMap.payload.us_privacy = bidderRequest.uspConsent - } - - rMap.shortCodes.push(b.params.sc); - rMap.payload[b.params.sc] = b.bidId; - - if ((typeof b.params.host === 'string') && b.params.host.length && - (b.params.host !== rMap.endpoint)) { - rMap.endpoint = b.params.host; - } - }); - - utils._each(auctionCodes, function(auctionId) { - var req = requestsMap[auctionId]; - requests.push({ - method: 'GET', - url: `https://${req.endpoint}/${auctionId}/${req.shortCodes.join('_')}/aardvark`, - data: req.payload, - bidderRequest - }); - }); - - return requests; - }, - - interpretResponse: function(serverResponse, bidRequest) { - var bidResponses = []; - - if (!Array.isArray(serverResponse.body)) { - serverResponse.body = [serverResponse.body]; - } - - utils._each(serverResponse.body, function(rawBid) { - var cpm = +(rawBid.cpm || 0); - - if (!cpm) { - return; - } - - var bidResponse = { - requestId: rawBid.cid, - cpm: cpm, - width: rawBid.width || 0, - height: rawBid.height || 0, - currency: rawBid.currency ? rawBid.currency : AARDVARK_CURRENCY, - netRevenue: rawBid.netRevenue ? rawBid.netRevenue : true, - ttl: rawBid.ttl ? rawBid.ttl : AARDVARK_TTL, - creativeId: rawBid.creativeId || 0 - }; - - if (rawBid.hasOwnProperty('dealId')) { - bidResponse.dealId = rawBid.dealId - } - - if (rawBid.hasOwnProperty('ex')) { - bidResponse.ex = rawBid.ex; - } - - switch (rawBid.media) { - case 'banner': - bidResponse.ad = rawBid.adm + utils.createTrackPixelHtml(decodeURIComponent(rawBid.nurl)); - break; - - default: - return utils.logError('bad Aardvark response (media)', rawBid); - } - - bidResponses.push(bidResponse); - }); - - return bidResponses; - }, - - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - const syncs = []; - const params = []; - var gdprApplies = false; - if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { - gdprApplies = gdprConsent.gdprApplies; - } - - if (!syncOptions.iframeEnabled) { - utils.logWarn('Aardvark: Please enable iframe based user sync.'); - return syncs; - } - - if (hasSynced) { - return syncs; - } - - hasSynced = true; - if (gdprApplies) { - params.push(['g', '1']); - params.push(['c', gdprConsent.consentString]); - } - - if (uspConsent) { - params.push(['us_privacy', uspConsent]); - } - - var queryStr = ''; - if (params.length) { - queryStr = '?' + params.map(p => p[0] + '=' + encodeURIComponent(p[1])).join('&') - } - - syncs.push({ - type: 'iframe', - url: `https://${SYNC_ENDPOINT}/cs${queryStr}` - }); - return syncs; - }, - - /** - * Serializes schain params according to OpenRTB requirements - * @param {Object} supplyChain - * @returns {String} - */ - serializeSupplyChain: function (supplyChain) { - if (!hasValidSupplyChainParams(supplyChain)) { - return ''; - } - - return `${supplyChain.ver},${supplyChain.complete}!${spec.serializeSupplyChainNodes(supplyChain.nodes)}`; - }, - - /** - * Properly sorts schain object params - * @param {Array} nodes - * @returns {String} - */ - serializeSupplyChainNodes: function (nodes) { - const nodePropOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; - return nodes.map(node => { - return nodePropOrder.map(prop => encodeURIComponent(node[prop] || '')).join(','); - }).join('!'); - }, -}; - -/** - * Make sure the required params are present - * @param {Object} schain - * @param {Bool} - */ -export function hasValidSupplyChainParams(schain) { - if (!schain || !schain.nodes) { - return false; - } - const requiredFields = ['asi', 'sid', 'hp']; - - let isValid = schain.nodes.reduce((status, node) => { - if (!status) { - return status; - } - return requiredFields.every(field => node[field]); - }, true); - if (!isValid) { - utils.logError('Aardvark: required schain params missing'); - } - return isValid; -} - -registerBidder(spec); diff --git a/modules/ablidaBidAdapter.js b/modules/ablidaBidAdapter.js index 2400952367f..cb4f4ef2724 100644 --- a/modules/ablidaBidAdapter.js +++ b/modules/ablidaBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { triggerPixel } 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'; @@ -79,7 +79,7 @@ export const spec = { }, onBidWon: function (bid) { if (!bid['nurl']) { return; } - utils.triggerPixel(bid['nurl']); + triggerPixel(bid['nurl']); } }; diff --git a/modules/adWMGBidAdapter.js b/modules/adWMGBidAdapter.js index 689e7d02124..7bf6c703a55 100644 --- a/modules/adWMGBidAdapter.js +++ b/modules/adWMGBidAdapter.js @@ -1,6 +1,6 @@ 'use strict'; -import * as utils from '../src/utils.js'; +import { tryAppendQueryString } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; @@ -116,6 +116,10 @@ export const spec = { netRevenue: response.netRevenue, ttl: response.ttl, ad: response.ad, + meta: { + advertiserDomains: response.adomain && response.adomain.length ? response.adomain : [], + mediaType: 'banner' + } }; bidResponses.push(bidResponse); } @@ -124,11 +128,11 @@ export const spec = { }, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { if (gdprConsent && SYNC_ENDPOINT.indexOf('gdpr') === -1) { - SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); + SYNC_ENDPOINT = tryAppendQueryString(SYNC_ENDPOINT, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); } if (gdprConsent && typeof gdprConsent.consentString === 'string' && SYNC_ENDPOINT.indexOf('gdpr_consent') === -1) { - SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'gdpr_consent', gdprConsent.consentString); + SYNC_ENDPOINT = tryAppendQueryString(SYNC_ENDPOINT, 'gdpr_consent', gdprConsent.consentString); } if (SYNC_ENDPOINT.slice(-1) === '&') { @@ -136,7 +140,7 @@ export const spec = { } /* if (uspConsent) { - SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'us_privacy', uspConsent); + SYNC_ENDPOINT = tryAppendQueryString(SYNC_ENDPOINT, 'us_privacy', uspConsent); } */ let syncs = []; if (syncOptions.iframeEnabled) { diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index fd7a742d9e7..f929f7e660b 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -5,7 +5,7 @@ import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import * as utils from '../src/utils.js'; +import { getWindowTop } from '../src/utils.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -13,12 +13,12 @@ const events = Object.keys(CONSTANTS.EVENTS).map(key => CONSTANTS.EVENTS[key]); const VERSION = '2.0.0'; const adagioEnqueue = function adagioEnqueue(action, data) { - utils.getWindowTop().ADAGIO.queue.push({ action, data, ts: Date.now() }); + getWindowTop().ADAGIO.queue.push({ action, data, ts: Date.now() }); } function canAccessTopWindow() { try { - if (utils.getWindowTop().location.href) { + if (getWindowTop().location.href) { return true; } } catch (error) { @@ -41,7 +41,7 @@ adagioAdapter.enableAnalytics = config => { return; } - const w = utils.getWindowTop(); + const w = getWindowTop(); w.ADAGIO = w.ADAGIO || {}; w.ADAGIO.queue = w.ADAGIO.queue || []; diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 134303bf400..3381f00ff8f 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1,57 +1,55 @@ import find from 'core-js-pure/features/array/find.js'; -import * as utils from '../src/utils.js'; +import { + isInteger, isArray, deepAccess, mergeDeep, logWarn, logInfo, logError, getWindowTop, getWindowSelf, generateUUID, _map, + getDNT, parseUrl, getUniqueIdentifierStr, isNumber, cleanObj, isFn, inIframe, deepClone, getGptSlotInfoForAdUnitCode +} from '../src/utils.js'; import { config } from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { loadExternalScript } from '../src/adloader.js'; -import JSEncrypt from 'jsencrypt/bin/jsencrypt.js'; -import sha256 from 'crypto-js/sha256.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'; - -export const BIDDER_CODE = 'adagio'; -export const LOG_PREFIX = 'Adagio:'; -export const VERSION = '2.8.0'; -export const FEATURES_VERSION = '1'; +const BIDDER_CODE = 'adagio'; +const LOG_PREFIX = 'Adagio:'; +const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; -export const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; -export const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; -export const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; -export const GVLID = 617; +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 RENDERER_URL = 'https://script.4dex.io/outstream-player.js'; -export const MAX_SESS_DURATION = 30 * 60 * 1000; -export const ADAGIO_PUBKEY = `-----BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9el0+OEn6fvEh1RdVHQu4cnT0 -jFSzIbGJJyg3cKqvtE6A0iaz9PkIdJIvSSSNrmJv+lRGKPEyRA/VnzJIieL39Ngl -t0b0lsHN+W4n9kitS/DZ/xnxWK/9vxhv0ZtL1LL/rwR5Mup7rmJbNtDoNBw4TIGj -pV6EP3MTLosuUEpLaQIDAQAB ------END PUBLIC KEY-----`; +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. // https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf export const ORTB_VIDEO_PARAMS = { 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), - 'minduration': (value) => utils.isInteger(value), - 'maxduration': (value) => utils.isInteger(value), + 'minduration': (value) => isInteger(value), + 'maxduration': (value) => isInteger(value), 'protocols': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].indexOf(v) !== -1), - 'w': (value) => utils.isInteger(value), - 'h': (value) => utils.isInteger(value), - 'startdelay': (value) => utils.isInteger(value), + 'w': (value) => isInteger(value), + 'h': (value) => isInteger(value), + 'startdelay': (value) => isInteger(value), 'placement': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5].indexOf(v) !== -1), 'linearity': (value) => [1, 2].indexOf(value) !== -1, 'skip': (value) => [0, 1].indexOf(value) !== -1, - 'skipmin': (value) => utils.isInteger(value), - 'skipafter': (value) => utils.isInteger(value), - 'sequence': (value) => utils.isInteger(value), + 'skipmin': (value) => isInteger(value), + 'skipafter': (value) => isInteger(value), + 'sequence': (value) => isInteger(value), 'battr': (value) => Array.isArray(value) && value.every(v => Array.from({length: 17}, (_, i) => i + 1).indexOf(v) !== -1), - 'maxextended': (value) => utils.isInteger(value), - 'minbitrate': (value) => utils.isInteger(value), - 'maxbitrate': (value) => utils.isInteger(value), + 'maxextended': (value) => isInteger(value), + 'minbitrate': (value) => isInteger(value), + 'maxbitrate': (value) => isInteger(value), 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, 'playbackmethod': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].indexOf(v) !== -1), 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, @@ -62,38 +60,94 @@ export const ORTB_VIDEO_PARAMS = { let currentWindow; -const EXT_DATA = {} +export const GlobalExchange = (function() { + let features; + let exchangeData = {}; + + return { + clearFeatures: function() { + features = undefined; + }, + + clearExchangeData: function() { + exchangeData = {}; + }, + + getOrSetGlobalFeatures: function () { + if (!features) { + features = { + page_dimensions: getPageDimensions().toString(), + viewport_dimensions: getViewPortDimensions().toString(), + user_timestamp: getTimestampUTC().toString(), + dom_loading: getDomLoadingDuration().toString(), + } + } + return features; + }, + + prepareExchangeData(storageValue) { + const adagioStorage = JSON.parse(storageValue, function(name, value) { + if (name.charAt(0) !== '_' || name === '') { + return value; + } + }); + let random = deepAccess(adagioStorage, 'session.rnd'); + let newSession = false; + + if (internal.isNewSession(adagioStorage)) { + newSession = true; + random = Math.random(); + } + + const data = { + session: { + new: newSession, + rnd: random + } + } + + mergeDeep(exchangeData, adagioStorage, data); + + internal.enqueue({ + action: 'session', + ts: Date.now(), + data: exchangeData + }); + }, + + getExchangeData() { + return exchangeData + } + }; +})(); export function adagioScriptFromLocalStorageCb(ls) { try { if (!ls) { - utils.logWarn(`${LOG_PREFIX} script not found.`); + logWarn(`${LOG_PREFIX} script not found.`); return; } const hashRgx = /^(\/\/ hash: (.+)\n)(.+\n)$/; if (!hashRgx.test(ls)) { - utils.logWarn(`${LOG_PREFIX} no hash found.`); + logWarn(`${LOG_PREFIX} no hash found.`); storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY); } else { const r = ls.match(hashRgx); const hash = r[2]; const content = r[3]; - var jsEncrypt = new JSEncrypt(); - jsEncrypt.setPublicKey(ADAGIO_PUBKEY); - - if (jsEncrypt.verify(content, hash, sha256)) { - utils.logInfo(`${LOG_PREFIX} start script.`); + if (verify(content, hash, ADAGIO_PUBKEY, ADAGIO_PUBKEY_E)) { + logInfo(`${LOG_PREFIX} start script.`); Function(ls)(); // eslint-disable-line no-new-func } else { - utils.logWarn(`${LOG_PREFIX} invalid script found.`); + logWarn(`${LOG_PREFIX} invalid script found.`); storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY); } } } catch (err) { - utils.logError(LOG_PREFIX, err); + logError(LOG_PREFIX, err); } } @@ -106,20 +160,25 @@ export function getAdagioScript() { if (isValid) { loadExternalScript(ADAGIO_TAG_URL, BIDDER_CODE); } else { - // ensure adagio removing for next time. - // It's an antipattern regarding the TCF2 enforcement logic - // but it's the only way to respect the user choice update. - window.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); - // Extra data from external script. - // This key is removed only if localStorage is not accessible. - window.localStorage.removeItem('adagio'); + // Try-catch to avoid error when 3rd party cookies is disabled (e.g. in privacy mode) + try { + // ensure adagio removing for next time. + // It's an antipattern regarding the TCF2 enforcement logic + // but it's the only way to respect the user choice update. + window.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); + // Extra data from external script. + // This key is removed only if localStorage is not accessible. + window.localStorage.removeItem('adagio'); + } catch (e) { + logInfo(`${LOG_PREFIX} unable to clear Adagio scripts from localstorage.`); + } } }); } function canAccessTopWindow() { try { - if (utils.getWindowTop().location.href) { + if (getWindowTop().location.href) { return true; } } catch (error) { @@ -128,48 +187,17 @@ function canAccessTopWindow() { } function getCurrentWindow() { - return currentWindow || utils.getWindowSelf(); + return currentWindow || getWindowSelf(); } function isSafeFrameWindow() { - const ws = utils.getWindowSelf(); + const ws = getWindowSelf(); return !!(ws.$sf && ws.$sf.ext); } -// Get localStorage "adagio" data to be passed to the request -export function prepareExchange(storageValue) { - const adagioStorage = JSON.parse(storageValue, function(name, value) { - if (!name.startsWith('_') || name === '') { - return value; - } - }); - let random = utils.deepAccess(adagioStorage, 'session.rnd'); - let newSession = false; - - if (internal.isNewSession(adagioStorage)) { - newSession = true; - random = Math.random(); - } - - const data = { - session: { - new: newSession, - rnd: random - } - } - - utils.mergeDeep(EXT_DATA, adagioStorage, data); - - internal.enqueue({ - action: 'session', - ts: Date.now(), - data: EXT_DATA - }); -} - function initAdagio() { if (canAccessTopWindow()) { - currentWindow = (canAccessTopWindow()) ? utils.getWindowTop() : utils.getWindowSelf(); + currentWindow = (canAccessTopWindow()) ? getWindowTop() : getWindowSelf(); } const w = internal.getCurrentWindow(); @@ -179,225 +207,20 @@ function initAdagio() { w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; w.ADAGIO.queue = w.ADAGIO.queue || []; w.ADAGIO.versions = w.ADAGIO.versions || {}; - w.ADAGIO.versions.adagioBidderAdapter = VERSION; + w.ADAGIO.versions.pbjs = '$prebid.version$'; w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow(); storage.getDataFromLocalStorage('adagio', (storageData) => { try { - internal.prepareExchange(storageData); + GlobalExchange.prepareExchangeData(storageData); } catch (e) { - utils.logError(LOG_PREFIX, e); + logError(LOG_PREFIX, e); } }); getAdagioScript(); } -export const _features = { - getPrintNumber(adUnitCode) { - const adagioAdUnit = internal.getOrAddAdagioAdUnit(adUnitCode); - return adagioAdUnit.printNumber || 1; - }, - - getPageDimensions() { - if (isSafeFrameWindow() || !canAccessTopWindow()) { - return ''; - } - - // the page dimension can be computed on window.top only. - const wt = utils.getWindowTop(); - const body = wt.document.querySelector('body'); - - if (!body) { - return ''; - } - const html = wt.document.documentElement; - const pageWidth = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth); - const pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); - - return `${pageWidth}x${pageHeight}`; - }, - - getViewPortDimensions() { - if (!isSafeFrameWindow() && !canAccessTopWindow()) { - return ''; - } - - const viewportDims = { w: 0, h: 0 }; - - if (isSafeFrameWindow()) { - const ws = utils.getWindowSelf(); - - if (typeof ws.$sf.ext.geom !== 'function') { - utils.logWarn(`${LOG_PREFIX} cannot use the $sf.ext.geom() safeFrame API method`); - return ''; - } - - const sfGeom = ws.$sf.ext.geom().win; - viewportDims.w = Math.round(sfGeom.w); - viewportDims.h = Math.round(sfGeom.h); - } else { - // window.top based computing - const wt = utils.getWindowTop(); - - if (wt.innerWidth) { - viewportDims.w = wt.innerWidth; - viewportDims.h = wt.innerHeight; - } else { - const d = wt.document; - const body = d.querySelector('body'); - - if (!body) { - return ''; - } - - viewportDims.w = d.querySelector('body').clientWidth; - viewportDims.h = d.querySelector('body').clientHeight; - } - } - - return `${viewportDims.w}x${viewportDims.h}`; - }, - - /** - * domLoading feature is computed on window.top if reachable. - */ - getDomLoadingDuration() { - let domLoadingDuration = -1; - let performance; - - performance = (canAccessTopWindow()) ? utils.getWindowTop().performance : utils.getWindowSelf().performance; - - if (performance && performance.timing && performance.timing.navigationStart > 0) { - const val = performance.timing.domLoading - performance.timing.navigationStart; - if (val > 0) { - domLoadingDuration = val; - } - } - - return domLoadingDuration; - }, - - getSlotPosition(params) { - const { adUnitElementId, postBid } = params; - - if (!adUnitElementId) { - return ''; - } - - if (!isSafeFrameWindow() && !canAccessTopWindow()) { - return ''; - } - - const position = { x: 0, y: 0 }; - - if (isSafeFrameWindow()) { - const ws = utils.getWindowSelf(); - - if (typeof ws.$sf.ext.geom !== 'function') { - utils.logWarn(`${LOG_PREFIX} cannot use the $sf.ext.geom() safeFrame API method`); - return ''; - } - - const sfGeom = ws.$sf.ext.geom().self; - position.x = Math.round(sfGeom.t); - position.y = Math.round(sfGeom.l); - } else if (canAccessTopWindow()) { - // window.top based computing - const wt = utils.getWindowTop(); - const d = wt.document; - - let domElement; - - if (postBid === true) { - const ws = utils.getWindowSelf(); - const currentElement = ws.document.getElementById(adUnitElementId); - domElement = internal.getElementFromTopWindow(currentElement, ws); - } else { - domElement = wt.document.getElementById(adUnitElementId); - } - - if (!domElement) { - return ''; - } - - let box = domElement.getBoundingClientRect(); - - const docEl = d.documentElement; - const body = d.body; - const clientTop = d.clientTop || body.clientTop || 0; - const clientLeft = d.clientLeft || body.clientLeft || 0; - const scrollTop = wt.pageYOffset || docEl.scrollTop || body.scrollTop; - const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft; - - const elComputedStyle = wt.getComputedStyle(domElement, null); - const elComputedDisplay = elComputedStyle.display || 'block'; - const mustDisplayElement = elComputedDisplay === 'none'; - - if (mustDisplayElement) { - domElement.style = domElement.style || {}; - domElement.style.display = 'block'; - box = domElement.getBoundingClientRect(); - domElement.style.display = elComputedDisplay; - } - position.x = Math.round(box.left + scrollLeft - clientLeft); - position.y = Math.round(box.top + scrollTop - clientTop); - } else { - return ''; - } - - return `${position.x}x${position.y}`; - }, - - getTimestampUTC() { - // timestamp returned in seconds - return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; - }, - - getDevice() { - const ws = utils.getWindowSelf(); - const ua = ws.navigator.userAgent; - - if ((/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i).test(ua)) { - return 5; // "tablet" - } - if ((/Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/).test(ua)) { - return 4; // "phone" - } - return 2; // personal computers - }, - - getBrowser() { - const ws = utils.getWindowSelf(); - const ua = ws.navigator.userAgent; - const uaLowerCase = ua.toLowerCase(); - return /Edge\/\d./i.test(ua) ? 'edge' : uaLowerCase.indexOf('chrome') > 0 ? 'chrome' : uaLowerCase.indexOf('firefox') > 0 ? 'firefox' : uaLowerCase.indexOf('safari') > 0 ? 'safari' : uaLowerCase.indexOf('opera') > 0 ? 'opera' : uaLowerCase.indexOf('msie') > 0 || ws.MSStream ? 'ie' : 'unknow'; - }, - - getOS() { - const ws = utils.getWindowSelf(); - const ua = ws.navigator.userAgent; - const uaLowerCase = ua.toLowerCase(); - return uaLowerCase.indexOf('linux') > 0 ? 'linux' : uaLowerCase.indexOf('mac') > 0 ? 'mac' : uaLowerCase.indexOf('win') > 0 ? 'windows' : ''; - }, - - getUrl(refererInfo) { - // top has not been reached, it means we are not sure - // to get the proper page url. - if (!refererInfo.reachedTop) { - return; - } - return refererInfo.referer; - }, - - getUrlFromParams(params) { - const { postBidOptions } = params; - if (postBidOptions && postBidOptions.url) { - return postBidOptions.url; - } - } -}; - function enqueue(ob) { const w = internal.getCurrentWindow(); @@ -406,50 +229,21 @@ function enqueue(ob) { w.ADAGIO.queue.push(ob); }; -function getOrAddAdagioAdUnit(adUnitCode) { - const w = internal.getCurrentWindow(); - - w.ADAGIO = w.ADAGIO || {}; - - if (w.ADAGIO.adUnits[adUnitCode]) { - return w.ADAGIO.adUnits[adUnitCode]; - } - - return w.ADAGIO.adUnits[adUnitCode] = {}; -}; - function getPageviewId() { const w = internal.getCurrentWindow(); w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || utils.generateUUID(); + w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); return w.ADAGIO.pageviewId; }; -function computePrintNumber(adUnitCode) { - let printNumber = 1; - const w = internal.getCurrentWindow(); - - if ( - w.ADAGIO && - w.ADAGIO.adUnits && w.ADAGIO.adUnits[adUnitCode] && - w.ADAGIO.adUnits[adUnitCode].pageviewId === internal.getPageviewId() && - w.ADAGIO.adUnits[adUnitCode].printNumber - ) { - printNumber = parseInt(w.ADAGIO.adUnits[adUnitCode].printNumber, 10) + 1; - } - - return printNumber; -}; - function getDevice() { const language = navigator.language ? 'language' : 'userLanguage'; return { userAgent: navigator.userAgent, language: navigator[language], - deviceType: _features.getDevice(), - dnt: utils.getDNT() ? 1 : 0, + dnt: getDNT() ? 1 : 0, geo: {}, js: 1 }; @@ -463,20 +257,18 @@ function getSite(bidderRequest) { const { refererInfo } = bidderRequest; if (canAccessTopWindow()) { - const wt = utils.getWindowTop(); + const wt = getWindowTop(); domain = wt.location.hostname; page = wt.location.href; referrer = wt.document.referrer || ''; } else if (refererInfo.reachedTop) { - const url = utils.parseUrl(refererInfo.referer); + 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. - // As the isBidRequestValid returns false when it does not reach the referer - // this should never called. - const url = utils.parseUrl(refererInfo.stack[0]); + const url = parseUrl(refererInfo.stack[0]); domain = url.hostname; } @@ -489,9 +281,9 @@ function getSite(bidderRequest) { function getElementFromTopWindow(element, currentWindow) { try { - if (utils.getWindowTop() === currentWindow) { + if (getWindowTop() === currentWindow) { if (!element.getAttribute('id')) { - element.setAttribute('id', `adg-${utils.getUniqueIdentifierStr()}`); + element.setAttribute('id', `adg-${getUniqueIdentifierStr()}`); } return element; } else { @@ -506,97 +298,26 @@ function getElementFromTopWindow(element, currentWindow) { return getElementFromTopWindow(frame, currentWindow.parent); } } catch (err) { - utils.logWarn(`${LOG_PREFIX}`, err); + logWarn(`${LOG_PREFIX}`, err); return false; } }; -function autoDetectAdUnitElementId(adUnitCode) { - const autoDetectedAdUnit = utils.getGptSlotInfoForAdUnitCode(adUnitCode); - let adUnitElementId = null; +function autoDetectAdUnitElementIdFromGpt(adUnitCode) { + const autoDetectedAdUnit = getGptSlotInfoForAdUnitCode(adUnitCode); - if (autoDetectedAdUnit && autoDetectedAdUnit.divId) { - adUnitElementId = autoDetectedAdUnit.divId; + if (autoDetectedAdUnit.divId) { + return autoDetectedAdUnit.divId; } - - return adUnitElementId; -}; - -function autoDetectEnvironment() { - const device = _features.getDevice(); - let environment; - switch (device) { - case 2: - environment = 'desktop'; - break; - case 4: - environment = 'mobile'; - break; - case 5: - environment = 'tablet'; - break; - }; - return environment; -}; - -function supportIObs() { - const currentWindow = internal.getCurrentWindow(); - return !!(currentWindow && currentWindow.IntersectionObserver && currentWindow.IntersectionObserverEntry && - currentWindow.IntersectionObserverEntry.prototype && 'intersectionRatio' in currentWindow.IntersectionObserverEntry.prototype); -} - -function getFeatures(bidRequest, bidderRequest) { - const { adUnitCode, params } = bidRequest; - const { adUnitElementId } = params; - const { refererInfo } = bidderRequest; - - if (!adUnitElementId) { - utils.logWarn(`${LOG_PREFIX} unable to get params.adUnitElementId. Continue without tiv.`); - } - - const features = { - print_number: _features.getPrintNumber(adUnitCode).toString(), - page_dimensions: _features.getPageDimensions().toString(), - viewport_dimensions: _features.getViewPortDimensions().toString(), - dom_loading: _features.getDomLoadingDuration().toString(), - // layout: features.getLayout().toString(), - adunit_position: _features.getSlotPosition(params).toString(), - user_timestamp: _features.getTimestampUTC().toString(), - device: _features.getDevice().toString(), - url: _features.getUrl(refererInfo) || _features.getUrlFromParams(params) || '', - browser: _features.getBrowser(), - os: _features.getOS() - }; - - Object.keys(features).forEach((prop) => { - if (features[prop] === '') { - delete features[prop]; - } - }); - - const adUnitFeature = {}; - - adUnitFeature[adUnitElementId] = { - features: features, - version: FEATURES_VERSION - }; - - internal.enqueue({ - action: 'features', - ts: Date.now(), - data: adUnitFeature - }); - - return features; }; function isRendererPreferredFromPublisher(bidRequest) { // renderer defined at adUnit level - const adUnitRenderer = utils.deepAccess(bidRequest, 'renderer'); + const adUnitRenderer = deepAccess(bidRequest, 'renderer'); const hasValidAdUnitRenderer = !!(adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render); // renderer defined at adUnit.mediaTypes level - const mediaTypeRenderer = utils.deepAccess(bidRequest, 'mediaTypes.video.renderer'); + const mediaTypeRenderer = deepAccess(bidRequest, 'mediaTypes.video.renderer'); const hasValidMediaTypeRenderer = !!(mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render); return !!( @@ -612,37 +333,40 @@ function isRendererPreferredFromPublisher(bidRequest) { */ function isNewSession(adagioStorage) { const now = Date.now(); - const { lastActivityTime, vwSmplg } = utils.deepAccess(adagioStorage, 'session', {}); + const { lastActivityTime, vwSmplg } = deepAccess(adagioStorage, 'session', {}); return ( - !utils.isNumber(lastActivityTime) || - !utils.isNumber(vwSmplg) || + !isNumber(lastActivityTime) || + !isNumber(vwSmplg) || (now - lastActivityTime) > MAX_SESS_DURATION ) } +function setPlayerName(bidRequest) { + const playerName = (internal.isRendererPreferredFromPublisher(bidRequest)) ? 'other' : 'adagio'; + + if (playerName === 'other') { + logWarn(`${LOG_PREFIX} renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.`); + } + + return playerName; +} + export const internal = { enqueue, - getOrAddAdagioAdUnit, getPageviewId, - computePrintNumber, getDevice, getSite, getElementFromTopWindow, - autoDetectAdUnitElementId, - autoDetectEnvironment, - getFeatures, getRefererInfo, adagioScriptFromLocalStorageCb, getCurrentWindow, - supportIObs, canAccessTopWindow, isRendererPreferredFromPublisher, - isNewSession, - prepareExchange + isNewSession }; function _getGdprConsent(bidderRequest) { - if (!utils.deepAccess(bidderRequest, 'gdprConsent')) { + if (!deepAccess(bidderRequest, 'gdprConsent')) { return false; } @@ -653,25 +377,12 @@ function _getGdprConsent(bidderRequest) { allowAuctionWithoutConsent } = bidderRequest.gdprConsent; - const consent = {}; - - if (apiVersion !== undefined) { - consent.apiVersion = apiVersion; - } - - if (consentString !== undefined) { - consent.consentString = consentString; - } - - if (gdprApplies !== undefined) { - consent.consentRequired = (gdprApplies) ? 1 : 0; - } - - if (allowAuctionWithoutConsent !== undefined) { - consent.allowAuctionWithoutConsent = allowAuctionWithoutConsent ? 1 : 0; - } - - return consent; + return cleanObj({ + apiVersion, + consentString, + consentRequired: gdprApplies ? 1 : 0, + allowAuctionWithoutConsent: allowAuctionWithoutConsent ? 1 : 0 + }); } function _getCoppa() { @@ -681,24 +392,22 @@ function _getCoppa() { } function _getUspConsent(bidderRequest) { - return (utils.deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false; + return (deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false; } function _getSchain(bidRequest) { - if (utils.deepAccess(bidRequest, 'schain')) { - return bidRequest.schain; - } + return deepAccess(bidRequest, 'schain'); } function _getEids(bidRequest) { - if (utils.deepAccess(bidRequest, 'userId')) { + if (deepAccess(bidRequest, 'userId')) { return createEidsArray(bidRequest.userId); } } function _buildVideoBidRequest(bidRequest) { - const videoAdUnitParams = utils.deepAccess(bidRequest, 'mediaTypes.video', {}); - const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {}); + const videoAdUnitParams = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); const computedParams = {}; // Special case for playerSize. @@ -716,11 +425,7 @@ function _buildVideoBidRequest(bidRequest) { }; if (videoParams.context && videoParams.context === OUTSTREAM) { - bidRequest.mediaTypes.video.playerName = (internal.isRendererPreferredFromPublisher(bidRequest)) ? 'other' : 'adagio'; - - if (bidRequest.mediaTypes.video.playerName === 'other') { - utils.logWarn(`${LOG_PREFIX} renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.`); - } + bidRequest.mediaTypes.video.playerName = setPlayerName(bidRequest); } // Only whitelisted OpenRTB options need to be validated. @@ -732,7 +437,7 @@ function _buildVideoBidRequest(bidRequest) { bidRequest.mediaTypes.video[paramName] = videoParams[paramName]; } else { delete bidRequest.mediaTypes.video[paramName]; - utils.logWarn(`${LOG_PREFIX} The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`); + logWarn(`${LOG_PREFIX} The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`); } } }); @@ -743,14 +448,14 @@ function _renderer(bid) { if (typeof window.ADAGIO.outstreamPlayer === 'function') { window.ADAGIO.outstreamPlayer(bid); } else { - utils.logError(`${LOG_PREFIX} Adagio outstream player is not defined`); + logError(`${LOG_PREFIX} Adagio outstream player is not defined`); } }); } function _parseNativeBidResponse(bid) { if (!bid.admNative || !Array.isArray(bid.admNative.assets)) { - utils.logError(`${LOG_PREFIX} Invalid native response`); + logError(`${LOG_PREFIX} Invalid native response`); return; } @@ -806,8 +511,8 @@ function _parseNativeBidResponse(bid) { if (bid.admNative.link.url) { native.clickUrl = bid.admNative.link.url; } - if (Array.isArray(bid.admNative.link.clickTrackers)) { - native.clickTrackers = bid.admNative.link.clickTrackers + if (Array.isArray(bid.admNative.link.clicktrackers)) { + native.clickTrackers = bid.admNative.link.clicktrackers } } @@ -854,84 +559,323 @@ function _parseNativeBidResponse(bid) { bid.native = native } -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - supportedMediaTypes: SUPPORTED_MEDIA_TYPES, +function _getFloors(bidRequest) { + if (!isFn(bidRequest.getFloor)) { + return false; + } - isBidRequestValid(bid) { - const { adUnitCode, auctionId, sizes, bidder, params, mediaTypes } = bid; - if (!params) { - utils.logWarn(`${LOG_PREFIX} the "params" property is missing.`); - return false; - } + const floors = []; - const { organizationId, site } = params; - const adUnitElementId = (params.useAdUnitCodeAsAdUnitElementId === true) - ? adUnitCode - : params.adUnitElementId || internal.autoDetectAdUnitElementId(adUnitCode); - const placement = (params.useAdUnitCodeAsPlacement === true) ? adUnitCode : params.placement; - const environment = params.environment || internal.autoDetectEnvironment(); - const supportIObs = internal.supportIObs(); - - // insure auto-detected params are kept in `bid` object. - bid.params = { - ...params, - adUnitElementId, - environment, - placement, - supportIObs - }; - - const debugData = () => ({ - action: 'pb-dbg', - ts: Date.now(), - data: { - bid - } + const getAndPush = (mediaType, size) => { + const info = bidRequest.getFloor({ + currency: CURRENCY, + mediaType, + size: [] }); - const refererInfo = internal.getRefererInfo(); + 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 + })); + } - if (!refererInfo.reachedTop) { - utils.logWarn(`${LOG_PREFIX} the main page url is unreachabled.`); - internal.enqueue(debugData()); + Object.keys(bidRequest.mediaTypes).forEach(mediaType => { + if (SUPPORTED_MEDIA_TYPES.indexOf(mediaType) !== -1) { + const sizeProp = mediaType === VIDEO ? 'playerSize' : 'sizes'; - return false; - } else if (!(organizationId && site && placement)) { - utils.logWarn(`${LOG_PREFIX} at least one required param is missing.`); - internal.enqueue(debugData()); + if (bidRequest.mediaTypes[mediaType][sizeProp] && bidRequest.mediaTypes[mediaType][sizeProp].length) { + if (isArray(bidRequest.mediaTypes[mediaType][sizeProp][0])) { + bidRequest.mediaTypes[mediaType][sizeProp].forEach(size => { + getAndPush(mediaType, [size[0], size[1]]); + }); + } else { + getAndPush(mediaType, [bidRequest.mediaTypes[mediaType][sizeProp][0], bidRequest.mediaTypes[mediaType][sizeProp][1]]); + } + } else { + getAndPush(mediaType, '*'); + } + } + }); - return false; + return floors; +} + +/** + * Try to find the value of `paramName` and set it to adUnit.params if + * it has not already been set. + * This function will check through: + * - bidderSettings object + * - ortb2.site.ext.data FPD… + * + * @param {*} bid + * @param {String} paramName + */ +export function setExtraParam(bid, paramName) { + bid.params = bid.params || {}; + + // eslint-disable-next-line + if (!!(bid.params[paramName])) { + return; + } + + const adgGlobalConf = config.getConfig('adagio') || {}; + const ortb2Conf = config.getConfig('ortb2'); + + const detected = adgGlobalConf[paramName] || deepAccess(ortb2Conf, `site.ext.data.${paramName}`, null); + if (detected) { + bid.params[paramName] = detected; + } +} + +function autoFillParams(bid) { + // adUnitElementId … + const adgGlobalConf = config.getConfig('adagio') || {}; + + bid.params = bid.params || {}; + + // adgGlobalConf.siteId is a shortcut to facilitate the integration for publisher. + if (adgGlobalConf.siteId) { + bid.params.organizationId = adgGlobalConf.siteId.split(':')[0]; + bid.params.site = adgGlobalConf.siteId.split(':')[1]; + } + + // Edge case. Useful when Prebid Manager cannot handle properly params setting… + if (adgGlobalConf.useAdUnitCodeAsPlacement === true || bid.params.useAdUnitCodeAsPlacement === true) { + bid.params.placement = bid.adUnitCode; + } + + bid.params.adUnitElementId = deepAccess(bid, 'ortb2Imp.ext.data.elementId', null) || bid.params.adUnitElementId; + + if (!bid.params.adUnitElementId) { + if (adgGlobalConf.useAdUnitCodeAsAdUnitElementId === true || bid.params.useAdUnitCodeAsAdUnitElementId === true) { + bid.params.adUnitElementId = bid.adUnitCode; + } else { + bid.params.adUnitElementId = autoDetectAdUnitElementIdFromGpt(bid.adUnitCode); } + } - const w = internal.getCurrentWindow(); - const pageviewId = internal.getPageviewId(); - const printNumber = internal.computePrintNumber(adUnitCode); - - // Store adUnits config. - // If an adUnitCode has already been stored, it will be replaced. - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode); - w.ADAGIO.pbjsAdUnits.push({ - code: adUnitCode, - mediaTypes: mediaTypes || {}, - sizes: (mediaTypes && mediaTypes.banner && Array.isArray(mediaTypes.banner.sizes)) ? mediaTypes.banner.sizes : sizes, - bids: [{ - bidder, - params: bid.params // use the updated bid.params object with auto-detected params - }], - auctionId, - pageviewId, - printNumber - }); + // extra params + setExtraParam(bid, 'environment'); + setExtraParam(bid, 'pagetype'); + setExtraParam(bid, 'category'); + setExtraParam(bid, 'subcategory'); +} + +function getPageDimensions() { + if (isSafeFrameWindow() || !canAccessTopWindow()) { + return ''; + } + + // the page dimension can be computed on window.top only. + const wt = getWindowTop(); + const body = wt.document.querySelector('body'); + + if (!body) { + return ''; + } + const html = wt.document.documentElement; + const pageWidth = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth); + const pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); + + return `${pageWidth}x${pageHeight}`; +} + +/** +* @todo Move to prebid Core as Utils. +* @returns +*/ +function getViewPortDimensions() { + if (!isSafeFrameWindow() && !canAccessTopWindow()) { + return ''; + } + + const viewportDims = { w: 0, h: 0 }; + + if (isSafeFrameWindow()) { + const ws = getWindowSelf(); + + if (typeof ws.$sf.ext.geom !== 'function') { + logWarn(LOG_PREFIX, 'Unable to compute from safeframe api.'); + return ''; + } + + const sfGeom = ws.$sf.ext.geom(); + + if (!sfGeom || !sfGeom.win) { + logWarn(LOG_PREFIX, 'Unable to compute from safeframe api. Missing `geom().win` property'); + return ''; + } + + viewportDims.w = Math.round(sfGeom.w); + viewportDims.h = Math.round(sfGeom.h); + } else { + // window.top based computing + const wt = getWindowTop(); + viewportDims.w = wt.innerWidth; + viewportDims.h = wt.innerHeight; + } + + return `${viewportDims.w}x${viewportDims.h}`; +} + +function getSlotPosition(adUnitElementId) { + if (!adUnitElementId) { + return ''; + } + + if (!isSafeFrameWindow() && !canAccessTopWindow()) { + return ''; + } - // (legacy) Store internal adUnit information - w.ADAGIO.adUnits[adUnitCode] = { - auctionId, - pageviewId, - printNumber, - }; + const position = { x: 0, y: 0 }; + + if (isSafeFrameWindow()) { + const ws = getWindowSelf(); + + if (typeof ws.$sf.ext.geom !== 'function') { + logWarn(LOG_PREFIX, 'Unable to compute from safeframe api.'); + return ''; + } + + const sfGeom = ws.$sf.ext.geom(); + + if (!sfGeom || !sfGeom.self) { + logWarn(LOG_PREFIX, 'Unable to compute from safeframe api. Missing `geom().self` property'); + return ''; + } + + position.x = Math.round(sfGeom.t); + position.y = Math.round(sfGeom.l); + } else if (canAccessTopWindow()) { + // window.top based computing + const wt = getWindowTop(); + const d = wt.document; + + let domElement; + + if (inIframe() === true) { + const ws = getWindowSelf(); + const currentElement = ws.document.getElementById(adUnitElementId); + domElement = internal.getElementFromTopWindow(currentElement, ws); + } else { + domElement = wt.document.getElementById(adUnitElementId); + } + + if (!domElement) { + return ''; + } + + let box = domElement.getBoundingClientRect(); + + const docEl = d.documentElement; + const body = d.body; + const clientTop = d.clientTop || body.clientTop || 0; + const clientLeft = d.clientLeft || body.clientLeft || 0; + const scrollTop = wt.pageYOffset || docEl.scrollTop || body.scrollTop; + const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft; + + const elComputedStyle = wt.getComputedStyle(domElement, null); + const elComputedDisplay = elComputedStyle.display || 'block'; + const mustDisplayElement = elComputedDisplay === 'none'; + + if (mustDisplayElement) { + domElement.style = domElement.style || {}; + domElement.style.display = 'block'; + box = domElement.getBoundingClientRect(); + domElement.style.display = elComputedDisplay; + } + position.x = Math.round(box.left + scrollLeft - clientLeft); + position.y = Math.round(box.top + scrollTop - clientTop); + } else { + return ''; + } + + return `${position.x}x${position.y}`; +} + +function getTimestampUTC() { + // timestamp returned in seconds + return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; +} + +function getPrintNumber(adUnitCode, bidderRequest) { + if (!bidderRequest.bids || !bidderRequest.bids.length) { + return 1; + } + const adagioBid = find(bidderRequest.bids, bid => bid.adUnitCode === adUnitCode); + return adagioBid.bidRequestsCount || 1; +} + +/** + * domLoading feature is computed on window.top if reachable. + */ +function getDomLoadingDuration() { + let domLoadingDuration = -1; + let performance; + + performance = (canAccessTopWindow()) ? getWindowTop().performance : getWindowSelf().performance; + + if (performance && performance.timing && performance.timing.navigationStart > 0) { + const val = performance.timing.domLoading - performance.timing.navigationStart; + if (val > 0) { + domLoadingDuration = val; + } + } + + return domLoadingDuration; +} + +function storeRequestInAdagioNS(bidRequest) { + const w = getCurrentWindow(); + // Store adUnits config. + // If an adUnitCode has already been stored, it will be replaced. + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== bidRequest.adUnitCode); + + let printNumber + if (bidRequest.features && bidRequest.features.print_number) { + printNumber = bidRequest.features.print_number; + } else if (bidRequest.params.features && bidRequest.params.features.print_number) { + printNumber = bidRequest.params.features.print_number; + } + + w.ADAGIO.pbjsAdUnits.push({ + code: bidRequest.adUnitCode, + mediaTypes: bidRequest.mediaTypes || {}, + sizes: (bidRequest.mediaTypes && bidRequest.mediaTypes.banner && Array.isArray(bidRequest.mediaTypes.banner.sizes)) ? bidRequest.mediaTypes.banner.sizes : bidRequest.sizes, + bids: [{ + bidder: bidRequest.bidder, + params: bidRequest.params // use the updated bid.params object with auto-detected params + }], + auctionId: bidRequest.auctionId, + pageviewId: internal.getPageviewId(), + printNumber + }); + + // (legacy) Store internal adUnit information + w.ADAGIO.adUnits[bidRequest.adUnitCode] = { + auctionId: bidRequest.auctionId, + pageviewId: internal.getPageviewId(), + printNumber, + }; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + + isBidRequestValid(bid) { + bid.params = bid.params || {}; + + autoFillParams(bid); + + if (!(bid.params.organizationId && bid.params.site && bid.params.placement)) { + logWarn(`${LOG_PREFIX} at least one required param is missing.`); + // internal.enqueue(debugData()); + return false; + } return true; }, @@ -946,40 +890,74 @@ export const spec = { const coppa = _getCoppa(); const schain = _getSchain(validBidRequests[0]); const eids = _getEids(validBidRequests[0]) || []; - const adUnits = utils._map(validBidRequests, (bidRequest) => { - bidRequest.features = internal.getFeatures(bidRequest, bidderRequest); - if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { + const adUnits = _map(validBidRequests, (bidRequest) => { + const globalFeatures = GlobalExchange.getOrSetGlobalFeatures(); + const features = { + ...globalFeatures, + print_number: getPrintNumber(bidRequest.adUnitCode, bidderRequest).toString(), + adunit_position: getSlotPosition(bidRequest.params.adUnitElementId) // adUnitElementId à déplacer ??? + }; + + Object.keys(features).forEach((prop) => { + if (features[prop] === '') { + delete features[prop]; + } + }); + + bidRequest.features = features; + + internal.enqueue({ + action: 'features', + ts: Date.now(), + data: { + features: bidRequest.features, + params: bidRequest.params, + adUnitCode: bidRequest.adUnitCode + } + }); + + // Handle priceFloors module + bidRequest.floors = _getFloors(bidRequest); + + if (deepAccess(bidRequest, 'mediaTypes.video')) { _buildVideoBidRequest(bidRequest); } + storeRequestInAdagioNS(bidRequest); + return bidRequest; }); // Group ad units by organizationId const groupedAdUnits = adUnits.reduce((groupedAdUnits, adUnit) => { - adUnit.params.organizationId = adUnit.params.organizationId.toString(); + const adUnitCopy = deepClone(adUnit); + adUnitCopy.params.organizationId = adUnitCopy.params.organizationId.toString(); + + // remove useless props + delete adUnitCopy.floorData; + delete adUnitCopy.params.siteId; - groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || []; - groupedAdUnits[adUnit.params.organizationId].push(adUnit); + groupedAdUnits[adUnitCopy.params.organizationId] = groupedAdUnits[adUnitCopy.params.organizationId] || []; + groupedAdUnits[adUnitCopy.params.organizationId].push(adUnitCopy); return groupedAdUnits; }, {}); // Build one request per organizationId - const requests = utils._map(Object.keys(groupedAdUnits), organizationId => { + const requests = _map(Object.keys(groupedAdUnits), organizationId => { return { method: 'POST', url: ENDPOINT, data: { - id: utils.generateUUID(), + id: generateUUID(), organizationId: organizationId, secure: secure, device: device, site: site, pageviewId: pageviewId, adUnits: groupedAdUnits[organizationId], - data: EXT_DATA, + data: GlobalExchange.getExchangeData(), regs: { gdpr: gdprConsent, coppa: coppa, @@ -990,7 +968,6 @@ export const spec = { eids: eids }, prebidVersion: '$prebid.version$', - adapterVersion: VERSION, featuresVersion: FEATURES_VERSION }, options: { @@ -1019,8 +996,12 @@ export const spec = { const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); if (bidReq) { + bidObj.meta = deepAccess(bidObj, 'meta', {}); + bidObj.meta.mediaType = bidObj.mediaType; + bidObj.meta.advertiserDomains = (Array.isArray(bidObj.aDomain) && bidObj.aDomain.length) ? bidObj.aDomain : []; + if (bidObj.mediaType === VIDEO) { - const mediaTypeContext = utils.deepAccess(bidReq, 'mediaTypes.video.context'); + const mediaTypeContext = deepAccess(bidReq, 'mediaTypes.video.context'); // Adagio SSP returns a `vastXml` only. No `vastUrl` nor `videoCacheKey`. if (!bidObj.vastUrl && bidObj.vastXml) { bidObj.vastUrl = 'data:text/xml;charset=utf-8;base64,' + btoa(bidObj.vastXml.replace(/\\"/g, '"')); @@ -1032,8 +1013,8 @@ export const spec = { adUnitCode: bidObj.adUnitCode, url: bidObj.urlRenderer || RENDERER_URL, config: { - ...utils.deepAccess(bidReq, 'mediaTypes.video'), - ...utils.deepAccess(bidObj, 'outstream', {}) + ...deepAccess(bidReq, 'mediaTypes.video'), + ...deepAccess(bidObj, 'outstream', {}) } }); @@ -1057,7 +1038,7 @@ export const spec = { } } } catch (err) { - utils.logError(err); + logError(err); } return bidResponses; }, @@ -1074,6 +1055,45 @@ export const spec = { return syncs; }, + + /** + * Handle custom logic in s2s context + * + * @param {*} params + * @param {boolean} isOrtb Is an s2s context + * @param {*} adUnit + * @param {*} bidRequests + * @returns {object} updated params + */ + transformBidParams(params, isOrtb, adUnit, bidRequests) { + const adagioBidderRequest = find(bidRequests, bidRequest => bidRequest.bidderCode === 'adagio'); + const adagioBid = find(adagioBidderRequest.bids, bid => bid.adUnitCode === adUnit.code); + + if (isOrtb) { + autoFillParams(adagioBid); + + adagioBid.params.auctionId = deepAccess(adagioBidderRequest, 'auctionId'); + + const globalFeatures = GlobalExchange.getOrSetGlobalFeatures(); + adagioBid.params.features = { + ...globalFeatures, + print_number: getPrintNumber(adagioBid.adUnitCode, adagioBidderRequest).toString(), + adunit_position: getSlotPosition(adagioBid.params.adUnitElementId) // adUnitElementId à déplacer ??? + } + + adagioBid.params.pageviewId = internal.getPageviewId(); + adagioBid.params.prebidVersion = '$prebid.version$'; + adagioBid.params.data = GlobalExchange.getExchangeData(); + + if (deepAccess(adagioBid, 'mediaTypes.video.context') === OUTSTREAM) { + adagioBid.params.playerName = setPlayerName(adagioBid); + } + + storeRequestInAdagioNS(adagioBid); + } + + return adagioBid.params; + } }; initAdagio(); diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index 46656d88d37..889822d9bd4 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -8,187 +8,227 @@ Maintainer: dev@adagio.io Connects to Adagio demand source to fetch bids. -## Test Parameters +## Configuration + +Adagio require several params. These params must be set at Prebid.js global config level or at adUnit level. + +Below, the list of Adagio params and where they can be set. + +| Param name | Global config | AdUnit config | +| ---------- | ------------- | ------------- | +| siteId | x | +| organizationId (obsolete) | | x +| site (obsolete) | | x +| pagetype | x | x +| environment | x | x +| category | x | x +| subcategory | x | x +| useAdUnitCodeAsAdUnitElementId | x | x +| useAdUnitCodeAsPlacement | x | x +| placement | | x +| adUnitElementId | | x +| debug | | x +| video | | x +| native | | x + +### Global configuration + +The global configuration is used to store params once instead of duplicate them to each adUnit. The values will be used as "params" in the ad-request. ```javascript - var adUnits = [ - { - code: 'dfp_banniere_atf', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - } +pbjs.setConfig({ + debug: false, + // …, + adagio: { + siteId: '1002:adagio-io', // Required. Provided by Adagio + + // The following params are limited to 30 characters, + // and can only contain the following characters: + // - alphanumeric (A-Z+a-z+0-9, case-insensitive) + // - dashes `-` + // - underscores `_` + // Also, each param can have at most 50 unique active values (case-insensitive). + pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. + environment: 'mobile', // Recommended. Environment where the page is displayed. + category: 'sport', // Recommended. Category of the content displayed in the page. + subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value + }, +}); +``` + +### adUnit configuration + +```javascript +var adUnits = [ + { + code: 'dfp_banniere_atf', + bids: [{ + bidder: 'adagio', + params: { + placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + adUnitElementId: 'article_outstream', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + // Optional debug mode, used to get a bid response with expected cpm. + debug: { + enabled: true, + cpm: 3.00 // default to 1.00 }, - bids: [{ - bidder: 'adagio', // Required - params: { - organizationId: '1002', // Required - Organization ID provided by Adagio. - site: 'adagio-io', // Required - Site Name provided by Adagio. - placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. - adUnitElementId: 'article_outstream', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. - - // The following params are limited to 30 characters, - // and can only contain the following characters: - // - alphanumeric (A-Z+a-z+0-9, case-insensitive) - // - dashes `-` - // - underscores `_` - // Also, each param can have at most 50 unique active values (case-insensitive). - pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. - environment: 'mobile', // Recommended. Environment where the page is displayed. - category: 'sport', // Recommended. Category of the content displayed in the page. - subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. - postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value - // Optional debug mode, used to get a bid response with expected cpm. - debug: { - enabled: true, - cpm: 3.00 // default to 1.00 - } + video: { + skip: 0 + // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. + }, + native: { + // Optional OpenRTB Native 1.2 request object. Only `context`, `plcmttype` fields are supported. + context: 1, + plcmttype: 2 + }, + } + }] + } +]; +``` + +## Test Parameters + +```javascript + + pbjs.setConfig({ + debug: true, + adagio: { + pagetype: 'article', + environment: 'mobile', + category: 'sport', + subcategory: 'handball', + useAdUnitCodeAsAdUnitElementId: false, + useAdUnitCodeAsPlacement: false, + } + }); + + var adUnits = [ + { + code: 'dfp_banniere_atf', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bids: [{ + bidder: 'adagio', // Required + params: { + placement: 'in_article', + adUnitElementId: 'article_outstream', + debug: { + enabled: true, + cpm: 3.00 // default to 1.00 } - }] + } + }] + }, + { + code: 'article_outstream', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + skip: 1 + } }, - { - code: 'article_outstream', - mediaTypes: { + bids: [{ + bidder: 'adagio', + params: { + placement: 'in_article', + adUnitElementId: 'article_outstream', video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'], - skip: 1 - // Other OpenRTB 2.5 video options… + skip: 0 + }, + debug: { + enabled: true, + cpm: 3.00 } - }, - bids: [{ - bidder: 'adagio', // Required - params: { - organizationId: '1002', // Required - Organization ID provided by Adagio. - site: 'adagio-io', // Required - Site Name provided by Adagio. - placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. - adUnitElementId: 'article_outstream', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. - - // The following params are limited to 30 characters, - // and can only contain the following characters: - // - alphanumeric (A-Z+a-z+0-9, case-insensitive) - // - dashes `-` - // - underscores `_` - // Also, each param can have at most 50 unique active values (case-insensitive). - pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. - environment: 'mobile', // Recommended. Environment where the page is displayed. - category: 'sport', // Recommended. Category of the content displayed in the page. - subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. - postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value - video: { - skip: 0 - // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. - }, - // Optional debug mode, used to get a bid response with expected cpm. - debug: { - enabled: true, - cpm: 3.00 // default to 1.00 + } + }] + }, + { + code: 'article_native', + mediaTypes: { + native: { + // generic Prebid options + title: { + required: true, + len: 80 + }, + // … + // Custom Adagio data assets + ext: { + adagio_bvw: { + required: false } } - }] + } }, - { - code: 'article_native', - mediaTypes: { + bids: [{ + bidder: 'adagio', + params: { + placement: 'in_article', + adUnitElementId: 'article_native', native: { - // generic Prebid options - title: { - required: true, - len: 80 - }, - // … - // Custom Adagio data assets - ext: { - adagio_bvw: { - required: false - } - } + context: 1, + plcmttype: 2 + }, + debug: { + enabled: true, + cpm: 3.00 + } + } + }] + } + ]; + + pbjs.addAdUnits(adUnits); + + pbjs.bidderSettings = { + adagio: { + alwaysUseBid: true, + adserverTargeting: [ + { + key: "site", + val: function (bidResponse) { + return bidResponse.site; } }, - bids: [{ - bidder: 'adagio', // Required - params: { - organizationId: '1002', // Required - Organization ID provided by Adagio. - site: 'adagio-io', // Required - Site Name provided by Adagio. - placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. - adUnitElementId: 'article_native', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. - - // The following params are limited to 30 characters, - // and can only contain the following characters: - // - alphanumeric (A-Z+a-z+0-9, case-insensitive) - // - dashes `-` - // - underscores `_` - // Also, each param can have at most 50 unique active values (case-insensitive). - pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. - environment: 'mobile', // Recommended. Environment where the page is displayed. - category: 'sport', // Recommended. Category of the content displayed in the page. - subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. - postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value - // Optional OpenRTB Native 1.2 request object. Only `context`, `plcmttype` fields are supported. - native: { - context: 1, - plcmttype: 2 - }, - // Optional debug mode, used to get a bid response with expected cpm. - debug: { - enabled: true, - cpm: 3.00 // default to 1.00 - } + { + key: "environment", + val: function (bidResponse) { + return bidResponse.environment; } - }] - } - ]; - - pbjs.addAdUnits(adUnits); - - pbjs.bidderSettings = { - adagio: { - alwaysUseBid: true, - adserverTargeting: [ - { - key: "site", - val: function (bidResponse) { - return bidResponse.site; - } - }, - { - key: "environment", - val: function (bidResponse) { - return bidResponse.environment; - } - }, - { - key: "placement", - val: function (bidResponse) { - return bidResponse.placement; - } - }, - { - key: "pagetype", - val: function (bidResponse) { - return bidResponse.pagetype; - } - }, - { - key: "category", - val: function (bidResponse) { - return bidResponse.category; - } - }, - { - key: "subcategory", - val: function (bidResponse) { - return bidResponse.subcategory; - } + }, + { + key: "placement", + val: function (bidResponse) { + return bidResponse.placement; } - ] - } + }, + { + key: "pagetype", + val: function (bidResponse) { + return bidResponse.pagetype; + } + }, + { + key: "category", + val: function (bidResponse) { + return bidResponse.category; + } + }, + { + key: "subcategory", + val: function (bidResponse) { + return bidResponse.subcategory; + } + } + ] } + } ``` diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js new file mode 100644 index 00000000000..1b93d4fe1c6 --- /dev/null +++ b/modules/adbookpspBidAdapter.js @@ -0,0 +1,815 @@ +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 { + isPlainObject, deepSetValue, deepAccess, logWarn, inIframe, isNumber, logError, isArray, uniques, + flatten, triggerPixel, isStr, isEmptyStr, generateUUID +} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +/** + * CONSTANTS + */ + +export const VERSION = '1.0.0'; +const EXCHANGE_URL = 'https://ex.fattail.com/openrtb2'; +const WIN_TRACKING_URL = 'https://ev.fattail.com/wins'; +const BIDDER_CODE = 'adbookpsp'; +const USER_ID_KEY = 'hb_adbookpsp_uid'; +const USER_ID_COOKIE_EXP = 2592000000; // lasts 30 days +const BID_TTL = 300; +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +const DEFAULT_CURRENCY = 'USD'; +const VIDEO_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'protocols', + 'w', + 'h', + 'startdelay', + 'placement', + 'linearity', + 'skip', + 'skipmin', + 'skipafter', + 'sequence', + 'battr', + 'maxextended', + 'minbitrate', + 'maxbitrate', + 'boxingallowed', + 'playbackmethod', + 'playbackend', + 'delivery', + 'pos', + 'companionad', + 'api', + 'companiontype', + 'ext', +]; +const TARGETING_VALUE_SEPARATOR = ','; + +export const DEFAULT_BIDDER_CONFIG = { + bidTTL: BID_TTL, + defaultCurrency: DEFAULT_CURRENCY, + exchangeUrl: EXCHANGE_URL, + winTrackingEnabled: true, + winTrackingUrl: WIN_TRACKING_URL, + orgId: null, +}; + +config.setDefaults({ + adbookpsp: DEFAULT_BIDDER_CONFIG, +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + + buildRequests, + getUserSyncs, + interpretResponse, + isBidRequestValid, + onBidWon, +}; + +registerBidder(spec); + +/** + * BID REQUEST + */ + +function isBidRequestValid(bidRequest) { + return ( + hasRequiredParams(bidRequest) && + (isValidBannerRequest(bidRequest) || isValidVideoRequest(bidRequest)) + ); +} + +function buildRequests(validBidRequests, bidderRequest) { + const requests = []; + + if (validBidRequests.length > 0) { + requests.push({ + method: 'POST', + url: getBidderConfig('exchangeUrl'), + options: { + contentType: 'application/json', + withCredentials: true, + }, + data: buildRequest(validBidRequests, bidderRequest), + }); + } + + return requests; +} + +function buildRequest(validBidRequests, bidderRequest) { + const request = { + id: bidderRequest.bidderRequestId, + tmax: bidderRequest.timeout, + site: { + domain: window.location.hostname, + page: window.location.href, + ref: bidderRequest.refererInfo.referer, + }, + source: buildSource(validBidRequests, bidderRequest), + device: buildDevice(), + regs: buildRegs(bidderRequest), + user: buildUser(bidderRequest), + imp: validBidRequests.map(buildImp), + ext: { + adbook: { + config: getBidderConfig(), + version: { + prebid: '$prebid.version$', + adapter: VERSION, + }, + }, + }, + }; + + return JSON.stringify(request); +} + +function buildDevice() { + const { innerWidth, innerHeight } = common.getWindowDimensions(); + + const device = { + w: innerWidth, + h: innerHeight, + js: true, + ua: navigator.userAgent, + dnt: + navigator.doNotTrack === 'yes' || + navigator.doNotTrack == '1' || + navigator.msDoNotTrack == '1' + ? 1 + : 0, + }; + + const deviceConfig = common.getConfig('device'); + + if (isPlainObject(deviceConfig)) { + return { ...device, ...deviceConfig }; + } + + return device; +} + +function buildRegs(bidderRequest) { + const regs = { + coppa: common.getConfig('coppa') === true ? 1 : 0, + }; + + if (bidderRequest.gdprConsent) { + deepSetValue( + regs, + 'ext.gdpr', + bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + ); + deepSetValue( + regs, + 'ext.gdprConsentString', + bidderRequest.gdprConsent.consentString || '' + ); + } + + if (bidderRequest.uspConsent) { + deepSetValue(regs, 'ext.us_privacy', bidderRequest.uspConsent); + } + + return regs; +} + +function buildSource(bidRequests, bidderRequest) { + const source = { + fd: 1, + tid: bidderRequest.auctionId, + }; + const schain = deepAccess(bidRequests, '0.schain'); + + if (schain) { + deepSetValue(source, 'ext.schain', schain); + } + + return source; +} + +function buildUser(bidderRequest) { + const user = { + id: getUserId(), + }; + + if (bidderRequest.gdprConsent) { + user.gdprConsentString = bidderRequest.gdprConsent.consentString || ''; + } + + return user; +} + +function buildImp(bidRequest) { + let impBase = { + id: bidRequest.bidId, + tagid: bidRequest.adUnitCode, + ext: buildImpExt(bidRequest), + }; + + return Object.keys(bidRequest.mediaTypes) + .filter((mediaType) => includes(SUPPORTED_MEDIA_TYPES, mediaType)) + .reduce((imp, mediaType) => { + return { + ...imp, + [mediaType]: buildMediaTypeObject(mediaType, bidRequest), + }; + }, impBase); +} + +function buildMediaTypeObject(mediaType, bidRequest) { + switch (mediaType) { + case BANNER: + return buildBannerObject(bidRequest); + case VIDEO: + return buildVideoObject(bidRequest); + default: + logWarn(`${BIDDER_CODE}: Unsupported media type ${mediaType}!`); + } +} + +function buildBannerObject(bidRequest) { + const format = bidRequest.mediaTypes.banner.sizes.map((size) => { + const [w, h] = size; + + return { w, h }; + }); + const { w, h } = format[0]; + + return { + pos: 0, + topframe: inIframe() ? 0 : 1, + format, + w, + h, + }; +} + +function buildVideoObject(bidRequest) { + const { w, h } = getVideoSize(bidRequest); + let videoObj = { + w, + h, + }; + + for (const param of VIDEO_PARAMS) { + const paramsValue = deepAccess(bidRequest, `params.video.${param}`); + const mediaTypeValue = deepAccess( + bidRequest, + `mediaTypes.video.${param}` + ); + + if (paramsValue || mediaTypeValue) { + videoObj[param] = paramsValue || mediaTypeValue; + } + } + + return videoObj; +} + +function getVideoSize(bidRequest) { + const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize', [[]]); + const { w, h } = deepAccess(bidRequest, 'mediaTypes.video', {}); + + if (isNumber(w) && isNumber(h)) { + return { w, h }; + } + + return { + w: playerSize[0][0], + h: playerSize[0][1], + } +} + +function buildImpExt(validBidRequest) { + const defaultOrgId = getBidderConfig('orgId'); + const { orgId, placementId } = validBidRequest.params || {}; + const effectiverOrgId = orgId || defaultOrgId; + const ext = {}; + + if (placementId) { + deepSetValue(ext, 'adbook.placementId', placementId); + } + + if (effectiverOrgId) { + deepSetValue(ext, 'adbook.orgId', effectiverOrgId); + } + + return ext; +} + +/** + * BID RESPONSE + */ + +function interpretResponse(bidResponse, bidderRequest) { + const bidderRequestBody = safeJSONparse(bidderRequest.data); + + if ( + deepAccess(bidderRequestBody, 'id') != + deepAccess(bidResponse, 'body.id') + ) { + logError( + `${BIDDER_CODE}: Bid response id does not match bidder request id` + ); + + return []; + } + + const referrer = deepAccess(bidderRequestBody, 'site.ref', ''); + const incomingBids = deepAccess(bidResponse, 'body.seatbid', []) + .filter((seat) => isArray(seat.bid)) + .reduce((bids, seat) => bids.concat(seat.bid), []) + .filter(validateBid(bidderRequestBody)); + const targetingMap = buildTargetingMap(incomingBids); + + return impBidsToPrebidBids( + incomingBids, + bidderRequestBody, + bidResponse.body.cur, + referrer, + targetingMap + ); +} + +function impBidsToPrebidBids( + incomingBids, + bidderRequestBody, + bidResponseCurrency, + referrer, + targetingMap +) { + return incomingBids + .map( + impToPrebidBid( + bidderRequestBody, + bidResponseCurrency, + referrer, + targetingMap + ) + ) + .filter((i) => i !== null); +} + +const impToPrebidBid = + (bidderRequestBody, bidResponseCurrency, referrer, targetingMap) => (bid, bidIndex) => { + try { + const bidRequest = findBidRequest(bidderRequestBody, bid); + + if (!bidRequest) { + logError(`${BIDDER_CODE}: Could not match bid to bid request`); + + return null; + } + const categories = deepAccess(bid, 'cat', []); + const mediaType = getMediaType(bid.adm); + let prebidBid = { + ad: bid.adm, + adId: bid.adid, + adserverTargeting: targetingMap[bidIndex], + adUnitCode: bidRequest.tagid, + bidderRequestId: bidderRequestBody.id, + bidId: bid.id, + cpm: bid.price, + creativeId: bid.crid || bid.id, + currency: bidResponseCurrency || getBidderConfig('defaultCurrency'), + height: bid.h, + lineItemId: deepAccess(bid, 'ext.liid'), + mediaType, + meta: { + advertiserDomains: bid.adomain, + mediaType, + primaryCatId: categories[0], + secondaryCatIds: categories.slice(1), + }, + netRevenue: true, + nurl: bid.nurl, + referrer: referrer, + requestId: bid.impid, + ttl: getBidderConfig('bidTTL'), + width: bid.w, + }; + + if (mediaType === VIDEO) { + prebidBid = { + ...prebidBid, + ...getVideoSpecificParams(bidRequest, bid), + }; + } + + if (deepAccess(bid, 'ext.pa_win') === true) { + prebidBid.auctionWinner = true; + } + return prebidBid; + } catch (error) { + logError(`${BIDDER_CODE}: Error while building bid`, error); + + return null; + } + }; + +function getVideoSpecificParams(bidRequest, bid) { + return { + height: bid.h || bidRequest.video.h, + vastXml: bid.adm, + width: bid.w || bidRequest.video.w, + }; +} + +function buildTargetingMap(bids) { + const impIds = bids.map(({ impid }) => impid).filter(uniques); + const values = impIds.reduce((result, id) => { + result[id] = { + lineItemIds: [], + orderIds: [], + dealIds: [], + adIds: [], + adAndOrderIndexes: [] + }; + + return result; + }, {}); + + bids.forEach((bid, bidIndex) => { + let impId = bid.impid; + values[impId].lineItemIds.push(bid.ext.liid); + values[impId].dealIds.push(bid.dealid); + values[impId].adIds.push(bid.adid); + + if (deepAccess(bid, 'ext.ordid')) { + values[impId].orderIds.push(bid.ext.ordid); + bid.ext.ordid.split(TARGETING_VALUE_SEPARATOR).forEach((ordid, ordIndex) => { + let adIdIndex = values[impId].adIds.indexOf(bid.adid); + values[impId].adAndOrderIndexes.push(adIdIndex + '_' + ordIndex) + }) + } + }); + + const targetingMap = {}; + + bids.forEach((bid, bidIndex) => { + let id = bid.impid; + + targetingMap[bidIndex] = { + hb_liid_adbookpsp: values[id].lineItemIds.join(TARGETING_VALUE_SEPARATOR), + hb_deal_adbookpsp: values[id].dealIds.join(TARGETING_VALUE_SEPARATOR), + hb_ad_ord_adbookpsp: values[id].adAndOrderIndexes.join(TARGETING_VALUE_SEPARATOR), + hb_adid_c_adbookpsp: values[id].adIds.join(TARGETING_VALUE_SEPARATOR), + hb_ordid_adbookpsp: values[id].orderIds.join(TARGETING_VALUE_SEPARATOR), + }; + }) + return targetingMap; +} + +/** + * VALIDATION + */ + +function hasRequiredParams(bidRequest) { + const value = + deepAccess(bidRequest, 'params.placementId') != null || + deepAccess(bidRequest, 'params.orgId') != null || + getBidderConfig('orgId') != null; + + if (!value) { + logError(`${BIDDER_CODE}: missing orgId and placementId parameter`); + } + + return value; +} + +function isValidBannerRequest(bidRequest) { + const value = validateSizes( + deepAccess(bidRequest, 'mediaTypes.banner.sizes', []) + ); + + return value; +} + +function isValidVideoRequest(bidRequest) { + const value = + isArray(deepAccess(bidRequest, 'mediaTypes.video.mimes')) && + validateVideoSizes(bidRequest); + + return value; +} + +function validateSize(size) { + return isArray(size) && size.length === 2 && size.every(isNumber); +} + +function validateSizes(sizes) { + return isArray(sizes) && sizes.length > 0 && sizes.every(validateSize); +} + +function validateVideoSizes(bidRequest) { + const { w, h } = deepAccess(bidRequest, 'mediaTypes.video', {}); + + return ( + validateSizes( + deepAccess(bidRequest, 'mediaTypes.video.playerSize') + ) || + (isNumber(w) && isNumber(h)) + ); +} + +function validateBid(bidderRequestBody) { + return function (bid) { + const mediaType = getMediaType(bid.adm); + const bidRequest = findBidRequest(bidderRequestBody, bid); + let validators = commonBidValidators; + + if (mediaType === BANNER) { + validators = [...commonBidValidators, ...bannerBidValidators]; + } + + const value = validators.every((validator) => validator(bid, bidRequest)); + + if (!value) { + logWarn(`${BIDDER_CODE}: Invalid bid`, bid); + } + + return value; + }; +} + +const commonBidValidators = [ + (bid) => isPlainObject(bid), + (bid) => isNonEmptyStr(bid.adid), + (bid) => isNonEmptyStr(bid.adm), + (bid) => isNonEmptyStr(bid.id), + (bid) => isNonEmptyStr(bid.impid), + (bid) => isNonEmptyStr(deepAccess(bid, 'ext.liid')), + (bid) => isNumber(bid.price), +]; + +const bannerBidValidators = [ + validateBannerDimension('w'), + validateBannerDimension('h'), +]; + +function validateBannerDimension(dimension) { + return function (bid, bidRequest) { + if (bid[dimension] == null) { + return bannerHasSingleSize(bidRequest); + } + + return isNumber(bid[dimension]); + }; +} + +function bannerHasSingleSize(bidRequest) { + return deepAccess(bidRequest, 'banner.format', []).length === 1; +} + +/** + * USER SYNC + */ + +export const storage = getStorageManager(); + +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { + return responses + .map((response) => deepAccess(response, 'body.ext.sync')) + .filter(isArray) + .reduce(flatten, []) + .filter(validateSync(syncOptions)) + .map(applyConsents(gdprConsent, uspConsent)); +} + +const validateSync = (syncOptions) => (sync) => { + return ( + ((sync.type === 'image' && syncOptions.pixelEnabled) || + (sync.type === 'iframe' && syncOptions.iframeEnabled)) && + sync.url + ); +}; + +const applyConsents = (gdprConsent, uspConsent) => (sync) => { + const url = getUrlBuilder(sync.url); + + if (gdprConsent) { + url.set('gdpr', gdprConsent.gdprApplies ? 1 : 0); + url.set('consentString', gdprConsent.consentString || ''); + } + if (uspConsent) { + url.set('us_privacy', encodeURIComponent(uspConsent)); + } + if (common.getConfig('coppa') === true) { + url.set('coppa', 1); + } + + return { ...sync, url: url.toString() }; +}; + +function getUserId() { + const id = getUserIdFromStorage() || common.generateUUID(); + + setUserId(id); + + return id; +} + +function getUserIdFromStorage() { + const id = storage.localStorageIsEnabled() + ? storage.getDataFromLocalStorage(USER_ID_KEY) + : storage.getCookie(USER_ID_KEY); + + if (!validateUUID(id)) { + return; + } + + return id; +} + +function setUserId(userId) { + if (storage.localStorageIsEnabled()) { + storage.setDataInLocalStorage(USER_ID_KEY, userId); + } + + if (storage.cookiesAreEnabled()) { + const expires = new Date(Date.now() + USER_ID_COOKIE_EXP).toISOString(); + + storage.setCookie(USER_ID_KEY, userId, expires); + } +} + +function validateUUID(uuid) { + return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( + uuid + ); +} + +/** + * EVENT TRACKING + */ + +function onBidWon(bid) { + if (!getBidderConfig('winTrackingEnabled')) { + return; + } + + const wurl = buildWinUrl(bid); + + if (wurl !== null) { + triggerPixel(wurl); + } + + if (isStr(bid.nurl)) { + triggerPixel(bid.nurl); + } +} + +function buildWinUrl(bid) { + try { + const url = getUrlBuilder(getBidderConfig('winTrackingUrl')); + + url.set('impId', bid.requestId); + url.set('reqId', bid.bidderRequestId); + url.set('bidId', bid.bidId); + + return url.toString(); + } catch (_) { + logError( + `${BIDDER_CODE}: Could not build win tracking URL with %s`, + getBidderConfig('winTrackingUrl') + ); + + return null; + } +} + +/** + * COMMON + */ + +const VAST_REGEXP = /VAST\s+version/; + +function getMediaType(adm) { + const videoRegex = new RegExp(VAST_REGEXP); + + if (videoRegex.test(adm)) { + return VIDEO; + } + + const markup = safeJSONparse(adm.replace(/\\/g, '')); + + if (markup && isPlainObject(markup.native)) { + return NATIVE; + } + + return BANNER; +} + +function safeJSONparse(...args) { + try { + return JSON.parse(...args); + } catch (_) { + return undefined; + } +} + +function isNonEmptyStr(value) { + return isStr(value) && !isEmptyStr(value); +} + +function findBidRequest(bidderRequest, bid) { + return find(bidderRequest.imp, (imp) => imp.id === bid.impid); +} + +function getBidderConfig(property) { + if (!property) { + return common.getConfig(`${BIDDER_CODE}`); + } + + return common.getConfig(`${BIDDER_CODE}.${property}`); +} + +const getUrlBase = function (url) { + return url.split('?')[0]; +}; + +const getUrlQuery = function (url) { + const query = url.split('?')[1]; + + if (!query) { + return; + } + + return '?' + query.split('#')[0]; +}; + +const getUrlHash = function (url) { + const hash = url.split('#')[1]; + + if (!hash) { + return; + } + + return '#' + hash; +}; + +const getUrlBuilder = function (url) { + const hash = getUrlHash(url); + const base = getUrlBase(url); + const query = getUrlQuery(url); + const pairs = []; + + function set(key, value) { + pairs.push([key, value]); + + return { + set, + toString, + }; + } + + function toString() { + if (!pairs.length) { + return url; + } + + const queryString = pairs + .map(function (pair) { + return pair.join('='); + }) + .join('&'); + + if (!query) { + return base + '?' + queryString + (hash || ''); + } + + return base + query + '&' + queryString + (hash || ''); + } + + return { + set, + toString, + }; +}; + +export const common = { + generateUUID: function () { + return generateUUID(); + }, + getConfig: function (property) { + return config.getConfig(property); + }, + getWindowDimensions: function () { + return { + innerWidth: window.innerWidth, + innerHeight: window.innerHeight, + }; + }, +}; diff --git a/modules/adbookpspBidAdapter.md b/modules/adbookpspBidAdapter.md new file mode 100644 index 00000000000..e258b1fd7c3 --- /dev/null +++ b/modules/adbookpspBidAdapter.md @@ -0,0 +1,191 @@ +### Overview + +``` +Module Name: AdbookPSP Bid Adapter +Module Type: Bidder Adapter +Maintainer: hbsupport@fattail.com +``` + +### Description + +Prebid.JS adapter that connects to the AdbookPSP demand sources. + +*NOTE*: The AdBookPSP Bidder Adapter requires setup and approval before use. The adapter uses custom targeting keys that require a dedicated Google Ad Manager setup to work. Please reach out to your AdbookPSP representative for more details. + +### Bidder parameters + +Each adUnit with `adbookpsp` adapter has to have either `placementId` or `orgId` set. + +```js +var adUnits = [ + { + bids: [ + { + bidder: 'adbookpsp', + params: { + placementId: 'example-placement-id', + orgId: 'example-org-id', + }, + }, + ], + }, +]; +``` + +Alternatively, `orgId` can be set globally while configuring prebid.js: + +```js +pbjs.setConfig({ + adbookpsp: { + orgId: 'example-org-id', + }, +}); +``` + +*NOTE*: adUnit orgId will take precedence over the globally set orgId. + +#### Banner parameters + +Required: + +- sizes + +Example configuration: + +```js +var adUnits = [ + { + code: 'div-1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + } + }, +]; +``` + +#### Video parameters + +Required: + +- context +- mimes +- playerSize + +Additionaly, all `Video` object parameters described in chapter `3.2.7` of the [OpenRTB 2.5 specification](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf) can be passed as bidder params. + +Example configuration: + +```js +var adUnits = [ + { + code: 'div-1', + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4', 'video/x-flv'], + playerSize: [400, 300], + protocols: [2, 3], + }, + }, + bids: [ + { + bidder: 'adbookpsp', + params: { + placementId: 'example-placement-id', + video: { + placement: 2, + }, + }, + }, + ], + }, +]; +``` + +*NOTE*: Supporting outstream video requires the publisher to set up a renderer as described [in the Prebid docs](https://docs.prebid.org/dev-docs/show-outstream-video-ads.html). + +#### Testing params + +To test the adapter, either `placementId: 'example-placement-id'` or `orgId: 'example-org-id'` can be used. + +*NOTE*: If any adUnit uses the testing params, all adUnits will receive testing responses. + +Example adUnit configuration: + +```js +var adUnits = [ + { + code: 'div-1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'adbookpsp', + params: { + placementId: 'example-placement-id', + }, + }, + ], + }, +]; +``` + +Example google publisher tag configuration: + +```js +googletag + .defineSlot('/22094606581/example-adbookPSP', sizes, 'div-1') + .addService(googletag.pubads()); +``` + +### Configuration + +Setting of the `orgId` can be done in the `pbjs.setConfig()` call. If this is the case, both `orgId` and `placementId` become optional. Remember to only call `pbjs.setConfig()` once as each call overwrites anything set in previous calls. + +Enabling iframe based user syncs is also encouraged. + +```javascript +pbjs.setConfig({ + adbookpsp: { + orgId: 'example-org-id', + winTrackingEnabled: true, + }, + userSync: { + filterSettings: { + iframe: { + bidders: '*', + filter: 'include', + }, + }, + }, +}); +``` + +### Privacy + +GDPR and US Privacy are both supported by default. + +#### Event tracking + +This adapter tracks win events for it’s bids. This functionality can be disabled by adding `winTrackingEnabled: false` to the adapter configuration: + +```js +pbjs.setConfig({ + adbookpsp: { + winTrackingEnabled: false, + }, +}); +``` + +#### COPPA support + +COPPA support can be enabled for all the visitors by changing the config value: + +```js +config.setConfig({ coppa: true }); +``` diff --git a/modules/adbutlerBidAdapter.js b/modules/adbutlerBidAdapter.js deleted file mode 100644 index 10edd8ae3e3..00000000000 --- a/modules/adbutlerBidAdapter.js +++ /dev/null @@ -1,140 +0,0 @@ -'use strict'; - -import * as utils from '../src/utils.js'; -import {config} from '../src/config.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'adbutler'; - -export const spec = { - code: BIDDER_CODE, - pageID: Math.floor(Math.random() * 10e6), - aliases: ['divreach', 'doceree'], - - isBidRequestValid: function (bid) { - return !!(bid.params.accountID && bid.params.zoneID); - }, - - buildRequests: function (validBidRequests) { - let i; - let zoneID; - let bidRequest; - let accountID; - let keyword; - let domain; - let requestURI; - let serverRequests = []; - let zoneCounters = {}; - let extraParams = {}; - - for (i = 0; i < validBidRequests.length; i++) { - bidRequest = validBidRequests[i]; - zoneID = utils.getBidIdParameter('zoneID', bidRequest.params); - accountID = utils.getBidIdParameter('accountID', bidRequest.params); - keyword = utils.getBidIdParameter('keyword', bidRequest.params); - domain = utils.getBidIdParameter('domain', bidRequest.params); - extraParams = utils.getBidIdParameter('extra', bidRequest.params); - - if (!(zoneID in zoneCounters)) { - zoneCounters[zoneID] = 0; - } - - if (typeof domain === 'undefined' || domain.length === 0) { - domain = 'servedbyadbutler.com'; - } - - requestURI = 'https://' + domain + '/adserve/;type=hbr;'; - requestURI += 'ID=' + encodeURIComponent(accountID) + ';'; - requestURI += 'setID=' + encodeURIComponent(zoneID) + ';'; - requestURI += 'pid=' + encodeURIComponent(spec.pageID) + ';'; - requestURI += 'place=' + encodeURIComponent(zoneCounters[zoneID]) + ';'; - - // append the keyword for targeting if one was passed in - if (keyword !== '') { - requestURI += 'kw=' + encodeURIComponent(keyword) + ';'; - } - - for (let key in extraParams) { - if (extraParams.hasOwnProperty(key)) { - let val = encodeURIComponent(extraParams[key]); - requestURI += `${key}=${val};`; - } - } - - zoneCounters[zoneID]++; - serverRequests.push({ - method: 'GET', - url: requestURI, - data: {}, - bidRequest: bidRequest - }); - } - return serverRequests; - }, - - interpretResponse: function (serverResponse, bidRequest) { - const bidObj = bidRequest.bidRequest; - let bidResponses = []; - let bidResponse = {}; - let isCorrectSize = false; - let isCorrectCPM = true; - let CPM; - let minCPM; - let maxCPM; - let width; - let height; - - serverResponse = serverResponse.body; - if (serverResponse && serverResponse.status === 'SUCCESS' && bidObj) { - CPM = serverResponse.cpm; - minCPM = utils.getBidIdParameter('minCPM', bidObj.params); - maxCPM = utils.getBidIdParameter('maxCPM', bidObj.params); - width = parseInt(serverResponse.width); - height = parseInt(serverResponse.height); - - // Ensure response CPM is within the given bounds - if (minCPM !== '' && CPM < parseFloat(minCPM)) { - isCorrectCPM = false; - } - if (maxCPM !== '' && CPM > parseFloat(maxCPM)) { - isCorrectCPM = false; - } - - // Ensure that response ad matches one of the placement sizes. - utils._each(utils.deepAccess(bidObj, 'mediaTypes.banner.sizes', []), function (size) { - if (width === size[0] && height === size[1]) { - isCorrectSize = true; - } - }); - if (isCorrectCPM && isCorrectSize) { - bidResponse.requestId = bidObj.bidId; - bidResponse.bidderCode = bidObj.bidder; - bidResponse.creativeId = serverResponse.placement_id; - bidResponse.cpm = CPM; - bidResponse.width = width; - bidResponse.height = height; - bidResponse.ad = serverResponse.ad_code; - bidResponse.ad += spec.addTrackingPixels(serverResponse.tracking_pixels); - bidResponse.currency = 'USD'; - bidResponse.netRevenue = true; - bidResponse.ttl = config.getConfig('_bidderTimeout'); - bidResponse.referrer = utils.deepAccess(bidObj, 'refererInfo.referer'); - bidResponses.push(bidResponse); - } - } - return bidResponses; - }, - - addTrackingPixels: function (trackingPixels) { - let trackingPixelMarkup = ''; - utils._each(trackingPixels, function (pixelURL) { - let trackingPixel = ''; - - trackingPixelMarkup += trackingPixel; - }); - return trackingPixelMarkup; - } -}; -registerBidder(spec); diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js index 18cafe829b5..dcc453ef35a 100644 --- a/modules/addefendBidAdapter.js +++ b/modules/addefendBidAdapter.js @@ -19,6 +19,7 @@ export const spec = { v: $$PREBID_GLOBAL$$.version, auctionId: false, pageId: false, + gdpr_applies: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? bidderRequest.gdprConsent.gdprApplies : 'true', gdpr_consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : '', referer: bidderRequest.refererInfo.referer, bids: [], diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index 8b3550e6108..f0425a174ff 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -5,14 +5,20 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { - NATIVE + NATIVE, BANNER, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; +import { mergeDeep, _map, deepAccess, parseSizesInput, deepSetValue } from '../src/utils.js'; import { config } from '../src/config.js'; +import { Renderer } from '../src/Renderer.js'; + +const { getConfig } = config; const BIDDER_CODE = 'adf'; const GVLID = 50; -const BIDDER_ALIAS = [ { code: 'adformOpenRTB', gvlid: GVLID } ]; +const BIDDER_ALIAS = [ + { code: 'adformOpenRTB', gvlid: GVLID }, + { code: 'adform', gvlid: GVLID } +]; const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; const NATIVE_PARAMS = { title: { @@ -45,29 +51,79 @@ const NATIVE_PARAMS = { name: 'data' } }; +const OUTSTREAM_RENDERER_URL = 'https://s2.adform.net/banners/scripts/video/outstream/render.js'; export const spec = { code: BIDDER_CODE, aliases: BIDDER_ALIAS, gvlid: GVLID, - supportedMediaTypes: [ NATIVE ], - isBidRequestValid: bid => !!bid.params.mid, + supportedMediaTypes: [ NATIVE, BANNER, VIDEO ], + isBidRequestValid: (bid) => { + const params = bid.params || {}; + const { mid, inv, mname } = params; + return !!(mid || (inv && mname)); + }, buildRequests: (validBidRequests, bidderRequest) => { - const page = bidderRequest.refererInfo.referer; + let app, site; + + const commonFpd = getConfig('ortb2') || {}; + let { user } = commonFpd; + + if (typeof getConfig('app') === 'object') { + app = getConfig('app') || {}; + if (commonFpd.app) { + mergeDeep(app, commonFpd.app); + } + } else { + site = getConfig('site') || {}; + if (commonFpd.site) { + mergeDeep(site, commonFpd.site); + } + + if (!site.page) { + site.page = bidderRequest.refererInfo.referer; + } + } + + const device = getConfig('device') || {}; + device.w = device.w || window.innerWidth; + device.h = device.h || window.innerHeight; + device.ua = device.ua || navigator.userAgent; + const adxDomain = setOnAny(validBidRequests, 'params.adxDomain') || 'adx.adform.net'; - const ua = navigator.userAgent; + const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; - const tid = validBidRequests[0].transactionId; // ??? check with ssp + const tid = validBidRequests[0].transactionId; const test = setOnAny(validBidRequests, 'params.test'); - const publisher = setOnAny(validBidRequests, 'params.publisher'); - const siteId = setOnAny(validBidRequests, 'params.siteId'); - const currency = config.getConfig('currency.adServerCurrency'); + const currency = getConfig('currency.adServerCurrency'); const cur = currency && [ currency ]; const eids = setOnAny(validBidRequests, 'userIdAsEids'); + const schain = setOnAny(validBidRequests, 'schain'); const imp = validBidRequests.map((bid, id) => { bid.netRevenue = pt; - const assets = utils._map(bid.nativeParams, (bidParams, key) => { + + const floorInfo = bid.getFloor ? bid.getFloor({ + currency: currency || 'USD' + }) : {}; + const bidfloor = floorInfo.floor; + const bidfloorcur = floorInfo.currency; + const { mid, inv, mname } = bid.params; + + const imp = { + id: id + 1, + tagid: mid, + bidfloor, + bidfloorcur, + ext: { + bidder: { + inv, + mname + } + } + }; + + const assets = _map(bid.nativeParams, (bidParams, key) => { const props = NATIVE_PARAMS[key]; const asset = { required: bidParams.required & 1, @@ -102,21 +158,44 @@ export const spec = { } }).filter(Boolean); - return { - id: id + 1, - tagid: bid.params.mid, - native: { + if (assets.length) { + imp.native = { request: { assets } - } - }; + }; + } + + const bannerParams = deepAccess(bid, 'mediaTypes.banner'); + + if (bannerParams && bannerParams.sizes) { + const sizes = parseSizesInput(bannerParams.sizes); + const format = sizes.map(size => { + const [ width, height ] = size.split('x'); + const w = parseInt(width, 10); + const h = parseInt(height, 10); + return { w, h }; + }); + + imp.banner = { + format + }; + } + + const videoParams = deepAccess(bid, 'mediaTypes.video'); + if (videoParams) { + imp.video = videoParams; + } + + return imp; }); const request = { id: bidderRequest.auctionId, - site: { id: siteId, page, publisher }, - device: { ua }, + site, + app, + user, + device, source: { tid, fd: 1 }, ext: { pt }, cur, @@ -127,17 +206,21 @@ export const spec = { request.is_debug = !!test; request.test = 1; } - if (utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { - request.user = { ext: { consent: bidderRequest.gdprConsent.consentString } }; - request.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies & 1 } }; + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); } if (bidderRequest.uspConsent) { - utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); } if (eids) { - utils.deepSetValue(request, 'user.ext.eids', eids); + deepSetValue(request, 'user.ext.eids', eids); + } + + if (schain) { + deepSetValue(request, 'source.ext.schain', schain); } return { @@ -164,16 +247,36 @@ export const spec = { return bids.map((bid, id) => { const bidResponse = bidResponses[id]; if (bidResponse) { - return { + const mediaType = deepAccess(bidResponse, 'ext.prebid.type'); + const result = { requestId: bid.bidId, cpm: bidResponse.price, creativeId: bidResponse.crid, ttl: 360, netRevenue: bid.netRevenue === 'net', currency: cur, - mediaType: NATIVE, - native: parseNative(bidResponse) + mediaType, + width: bidResponse.w, + height: bidResponse.h, + dealId: bidResponse.dealid, + meta: { + mediaType, + advertiserDomains: bidResponse.adomain + } }; + + if (bidResponse.native) { + result.native = parseNative(bidResponse); + } else { + result[ mediaType === VIDEO ? 'vastXml' : 'ad' ] = bidResponse.adm; + } + + if (!bid.renderer && mediaType === VIDEO && deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { + result.renderer = Renderer.install({id: bid.bidId, url: OUTSTREAM_RENDERER_URL, adUnitCode: bid.adUnitCode}); + result.renderer.setRender(renderer); + } + + return result; } }).filter(Boolean); } @@ -202,7 +305,7 @@ function parseNative(bid) { function setOnAny(collection, key) { for (let i = 0, result; i < collection.length; i++) { - result = utils.deepAccess(collection[i], key); + result = deepAccess(collection[i], key); if (result) { return result; } @@ -212,3 +315,9 @@ function setOnAny(collection, key) { function flatten(arr) { return [].concat(...arr); } + +function renderer(bid) { + bid.renderer.push(() => { + window.Adform.renderOutstream(bid); + }); +} diff --git a/modules/adfBidAdapter.md b/modules/adfBidAdapter.md index 190aa91ea57..10264e6f486 100644 --- a/modules/adfBidAdapter.md +++ b/modules/adfBidAdapter.md @@ -7,15 +7,12 @@ Maintainer: Scope.FL.Scripts@adform.com # Description Module that connects to Adform demand sources to fetch bids. -Only native format is supported. Using OpenRTB standard. Previous adapter name - adformOpenRTB. +Banner, video and native formats are supported. Using OpenRTB standard. Previous adapter name - adformOpenRTB. # Test Parameters ``` - var adUnits = [ + var adUnits = [{ code: '/19968336/prebid_native_example_1', - sizes: [ - [360, 360] - ], mediaTypes: { native: { image: { @@ -44,16 +41,36 @@ Only native format is supported. Using OpenRTB standard. Previous adapter name - bids: [{ bidder: 'adf', params: { - mid: 606169, // required - adxDomain: 'adx.adform.net', // optional - siteId: '23455', // optional - priceType: 'gross' // optional, default is 'net' - publisher: { // optional block - id: "2706", - name: "Publishers Name", - domain: "publisher.com" - } + mid: 606169, // required + adxDomain: 'adx.adform.net' // optional + } + }] + }, { + code: '/19968336/prebid_banner_example_1', + mediaTypes: { + banner: { + sizes: [[ 300, 250 ]] + } + } + bids: [{ + bidder: 'adf', + params: { + mid: 1038466 + } + }] + }, { + code: '/19968336/prebid_video_example_1', + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + } + } + bids: [{ + bidder: 'adf', + params: { + mid: 822732 } }] - ]; + }]; ``` diff --git a/modules/adfinityBidAdapter.js b/modules/adfinityBidAdapter.js deleted file mode 100644 index ebf1198fba2..00000000000 --- a/modules/adfinityBidAdapter.js +++ /dev/null @@ -1,125 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; - -const BIDDER_CODE = 'adfinity'; -const AD_URL = 'https://stat.adfinity.pro/?c=o&m=multi'; -const SYNC_URL = 'https://stat.adfinity.pro/?c=o&m=cookie' - -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl); - case NATIVE: - const n = bid.native - return Boolean(n) && Boolean(n.title) && Boolean(n.body) && Boolean(n.image) && Boolean(n.impression_trackers); - default: - return false; - } -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placement_id)); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: (validBidRequests, bidderRequest) => { - let winTop = window; - let location; - try { - location = new URL(bidderRequest.refererInfo.referer) - winTop = window.top; - } catch (e) { - location = winTop.location; - utils.logMessage(e); - }; - let placements = []; - let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - - if (bidderRequest) { - if (bidderRequest.gdprConsent) { - request.gdpr_consent = bidderRequest.gdprConsent.consentString || 'ALL' - request.gdpr_require = bidderRequest.gdprConsent.gdprApplies ? 1 : 0 - } - } - - for (let i = 0; i < validBidRequests.length; i++) { - let bid = validBidRequests[i]; - let traff = bid.params.traffic || BANNER - let placement = { - placementId: bid.params.placement_id, - bidId: bid.bidId, - sizes: bid.mediaTypes[traff].sizes, - traffic: traff - }; - if (bid.schain) { - placement.schain = bid.schain; - } - placements.push(placement); - } - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: (serverResponse) => { - let response = []; - try { - serverResponse = serverResponse.body; - for (let i = 0; i < serverResponse.length; i++) { - let resItem = serverResponse[i]; - if (isBidResponseValid(resItem)) { - response.push(resItem); - } - } - } catch (e) { - utils.logMessage(e); - }; - return response; - }, - - getUserSyncs: () => { - return [{ - type: 'image', - url: SYNC_URL - }]; - } -}; - -registerBidder(spec); diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js deleted file mode 100644 index 3d0ed0fba79..00000000000 --- a/modules/adformBidAdapter.js +++ /dev/null @@ -1,213 +0,0 @@ -'use strict'; - -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { Renderer } from '../src/Renderer.js'; -import * as utils from '../src/utils.js'; -import includes from 'core-js-pure/features/array/includes.js'; - -const OUTSTREAM_RENDERER_URL = 'https://s2.adform.net/banners/scripts/video/outstream/render.js'; - -const BIDDER_CODE = 'adform'; -const GVLID = 50; -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - supportedMediaTypes: [ BANNER, VIDEO ], - isBidRequestValid: function (bid) { - return !!(bid.params.mid); - }, - buildRequests: function (validBidRequests, bidderRequest) { - var i, l, j, k, bid, _key, _value, reqParams, netRevenue, gdprObject; - const currency = config.getConfig('currency.adServerCurrency'); - const eids = getEncodedEIDs(utils.deepAccess(validBidRequests, '0.userIdAsEids')); - - var request = []; - var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'fd', 1 ], [ 'url', null ], [ 'tid', null ], [ 'eids', eids ] ]; - const targetingParams = { mkv: [], mkw: [], msw: [] }; - - var bids = JSON.parse(JSON.stringify(validBidRequests)); - var bidder = (bids[0] && bids[0].bidder) || BIDDER_CODE; - - // set common targeting options as query params - if (bids.length > 1) { - for (let key in targetingParams) { - if (targetingParams.hasOwnProperty(key)) { - const collection = bids.map(bid => ((bid.params[key] && bid.params[key].split(',')) || [])); - targetingParams[key] = collection.reduce(intersects); - if (targetingParams[key].length) { - bids.forEach((bid, index) => { - bid.params[key] = collection[index].filter(item => !includes(targetingParams[key], item)); - }); - } - } - } - } - - for (i = 0, l = bids.length; i < l; i++) { - bid = bids[i]; - if ((bid.params.priceType === 'net') || (bid.params.pt === 'net')) { - netRevenue = 'net'; - } - - for (j = 0, k = globalParams.length; j < k; j++) { - _key = globalParams[j][0]; - _value = bid[_key] || bid.params[_key]; - if (_value) { - bid[_key] = bid.params[_key] = null; - globalParams[j][1] = _value; - } - } - reqParams = bid.params; - reqParams.transactionId = bid.transactionId; - reqParams.rcur = reqParams.rcur || currency; - request.push(formRequestUrl(reqParams)); - } - - request.unshift('https://' + globalParams[0][1] + '/adx/?rp=4'); - netRevenue = netRevenue || 'gross'; - request.push('pt=' + netRevenue); - request.push('stid=' + validBidRequests[0].auctionId); - - const gdprApplies = utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); - const consentString = utils.deepAccess(bidderRequest, 'gdprConsent.consentString'); - if (gdprApplies !== undefined) { - gdprObject = { - gdpr: gdprApplies, - gdpr_consent: consentString - }; - request.push('gdpr=' + (gdprApplies & 1)); - request.push('gdpr_consent=' + consentString); - } - - if (bidderRequest && bidderRequest.uspConsent) { - request.push('us_privacy=' + bidderRequest.uspConsent); - } - - for (let key in targetingParams) { - if (targetingParams.hasOwnProperty(key)) { - globalParams.push([ key, targetingParams[key].join(',') ]); - } - } - - for (i = 1, l = globalParams.length; i < l; i++) { - _key = globalParams[i][0]; - _value = globalParams[i][1]; - if (_value) { - request.push(_key + '=' + encodeURIComponent(_value)); - } - } - - return { - method: 'GET', - url: request.join('&'), - bids: validBidRequests, - netRevenue: netRevenue, - bidder, - gdpr: gdprObject - }; - - function formRequestUrl(reqData) { - var key; - var url = []; - - for (key in reqData) { - if (reqData.hasOwnProperty(key) && reqData[key]) { url.push(key, '=', reqData[key], '&'); } - } - - return encodeURIComponent(btoa(url.join('').slice(0, -1))); - } - - function getEncodedEIDs(eids) { - if (utils.isArray(eids) && eids.length > 0) { - const parsed = parseEIDs(eids); - return btoa(JSON.stringify(parsed)); - } - } - - function parseEIDs(eids) { - return eids.reduce((result, eid) => { - const source = eid.source; - result[source] = result[source] || {}; - - eid.uids.forEach(value => { - const id = value.id + ''; - result[source][id] = result[source][id] || []; - result[source][id].push(value.atype); - }); - - return result; - }, {}); - } - - function intersects(col1, col2) { - return col1.filter(item => includes(col2, item)); - } - }, - interpretResponse: function (serverResponse, bidRequest) { - const VALID_RESPONSES = { - banner: 1, - vast_content: 1, - vast_url: 1 - }; - var bidObject, response, bid, type; - var bidRespones = []; - var bids = bidRequest.bids; - var responses = serverResponse.body; - for (var i = 0; i < responses.length; i++) { - response = responses[i]; - type = response.response === 'banner' ? BANNER : VIDEO; - bid = bids[i]; - if (VALID_RESPONSES[response.response] && (verifySize(response, utils.getAdUnitSizes(bid)) || type === VIDEO)) { - bidObject = { - requestId: bid.bidId, - cpm: response.win_bid, - width: response.width, - height: response.height, - creativeId: bid.bidId, - dealId: response.deal_id, - currency: response.win_cur, - netRevenue: bidRequest.netRevenue !== 'gross', - ttl: 360, - meta: { advertiserDomains: response && response.adomain ? response.adomain : [] }, - ad: response.banner, - bidderCode: bidRequest.bidder, - transactionId: bid.transactionId, - vastUrl: response.vast_url, - vastXml: response.vast_content, - mediaType: type - }; - - if (!bid.renderer && type === VIDEO && utils.deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { - bidObject.renderer = Renderer.install({id: bid.bidId, url: OUTSTREAM_RENDERER_URL}); - bidObject.renderer.setRender(renderer); - } - - if (bidRequest.gdpr) { - bidObject.gdpr = bidRequest.gdpr.gdpr; - bidObject.gdpr_consent = bidRequest.gdpr.gdpr_consent; - } - bidRespones.push(bidObject); - } - } - return bidRespones; - - function renderer(bid) { - bid.renderer.push(() => { - window.Adform.renderOutstream(bid); - }); - } - - function verifySize(adItem, validSizes) { - for (var j = 0, k = validSizes.length; j < k; j++) { - if (adItem.width == validSizes[j][0] && - adItem.height == validSizes[j][1]) { - return true; - } - } - return false; - } - } -}; -registerBidder(spec); diff --git a/modules/adformBidAdapter.md b/modules/adformBidAdapter.md deleted file mode 100644 index 10ebec37e08..00000000000 --- a/modules/adformBidAdapter.md +++ /dev/null @@ -1,30 +0,0 @@ -# Overview - -Module Name: Adform Bidder Adapter -Module Type: Bidder Adapter -Maintainer: Scope.FL.Scripts@adform.com - -# Description - -Module that connects to Adform demand sources to fetch bids. -Banner and video formats are supported. - -# Test Parameters -``` - var adUnits = [ - { - code: 'div-gpt-ad-1460505748561-0', - sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], // a display size - bids: [ - { - bidder: "adform", - params: { - adxDomain: 'adx.adform.net', //optional - mid: '292063', - priceType: 'net' // default is 'gross' - } - } - ] - }, - ]; -``` diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js index 7f92ab483f6..4dd320d3f24 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -1,7 +1,8 @@ -import * as utils from '../src/utils.js'; +import {tryAppendQueryString, getBidIdParameter} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; + const ADG_BIDDER_CODE = 'adgeneration'; export const spec = { @@ -24,7 +25,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - const ADGENE_PREBID_VERSION = '1.0.1'; + const ADGENE_PREBID_VERSION = '1.2.0'; let serverRequests = []; for (let i = 0, len = validBidRequests.length; i < len; i++) { const validReq = validBidRequests[i]; @@ -32,23 +33,23 @@ export const spec = { const URL = 'https://d.socdm.com/adsv/v1'; const url = validReq.params.debug ? DEBUG_URL : URL; let data = ``; - data = utils.tryAppendQueryString(data, 'posall', 'SSPLOC'); - const id = utils.getBidIdParameter('id', validReq.params); - data = utils.tryAppendQueryString(data, 'id', id); - data = utils.tryAppendQueryString(data, 'sdktype', '0'); - data = utils.tryAppendQueryString(data, 'hb', 'true'); - data = utils.tryAppendQueryString(data, 't', 'json3'); - data = utils.tryAppendQueryString(data, 'transactionid', validReq.transactionId); - data = utils.tryAppendQueryString(data, 'sizes', getSizes(validReq)); - data = utils.tryAppendQueryString(data, 'currency', getCurrencyType()); - data = utils.tryAppendQueryString(data, 'pbver', '$prebid.version$'); - data = utils.tryAppendQueryString(data, 'sdkname', 'prebidjs'); - data = utils.tryAppendQueryString(data, 'adapterver', ADGENE_PREBID_VERSION); + data = tryAppendQueryString(data, 'posall', 'SSPLOC'); + const id = getBidIdParameter('id', validReq.params); + data = tryAppendQueryString(data, 'id', id); + data = tryAppendQueryString(data, 'sdktype', '0'); + data = tryAppendQueryString(data, 'hb', 'true'); + data = tryAppendQueryString(data, 't', 'json3'); + data = tryAppendQueryString(data, 'transactionid', validReq.transactionId); + data = tryAppendQueryString(data, 'sizes', getSizes(validReq)); + data = tryAppendQueryString(data, 'currency', getCurrencyType()); + data = tryAppendQueryString(data, 'pbver', '$prebid.version$'); + data = tryAppendQueryString(data, 'sdkname', 'prebidjs'); + data = tryAppendQueryString(data, 'adapterver', ADGENE_PREBID_VERSION); // native以外にvideo等の対応が入った場合は要修正 if (!validReq.mediaTypes || !validReq.mediaTypes.native) { - data = utils.tryAppendQueryString(data, 'imark', '1'); + data = tryAppendQueryString(data, 'imark', '1'); } - data = utils.tryAppendQueryString(data, 'tp', bidderRequest.refererInfo.referer); + data = tryAppendQueryString(data, 'tp', bidderRequest.refererInfo.referer); // remove the trailing "&" if (data.lastIndexOf('&') === data.length - 1) { data = data.substring(0, data.length - 1); @@ -86,6 +87,11 @@ export const spec = { netRevenue: true, ttl: body.ttl || 10, }; + if (body.adomain && Array.isArray(body.adomain) && body.adomain.length) { + bidResponse.meta = { + advertiserDomains: body.adomain + } + } if (isNative(body)) { bidResponse.native = createNativeAd(body); bidResponse.mediaType = NATIVE; @@ -112,13 +118,25 @@ export const spec = { function createAd(body, bidRequest) { let ad = body.ad; if (body.vastxml && body.vastxml.length > 0) { - ad = `
${createAPVTag()}${insertVASTMethod(bidRequest.bidId, body.vastxml)}`; + if (isUpperBillboard(body)) { + const marginTop = bidRequest.params.marginTop ? bidRequest.params.marginTop : '0'; + ad = `${createADGBrowserMTag()}${insertVASTMethodForADGBrowserM(body.vastxml, marginTop)}`; + } else { + ad = `
${createAPVTag()}${insertVASTMethodForAPV(bidRequest.bidId, body.vastxml)}`; + } } ad = appendChildToBody(ad, body.beacon); if (removeWrapper(ad)) return removeWrapper(ad); return ad; } +function isUpperBillboard(body) { + if (body.location_params && body.location_params.option && body.location_params.option.ad_type) { + return body.location_params.option.ad_type === 'upper_billboard'; + } + return false; +} + function isNative(body) { if (!body) return false; return body.native_ad && body.native_ad.assets.length > 0; @@ -184,7 +202,12 @@ function createAPVTag() { return apvScript.outerHTML; } -function insertVASTMethod(targetId, vastXml) { +function createADGBrowserMTag() { + const ADGBrowserMURL = 'https://i.socdm.com/sdk/js/adg-browser-m.js'; + return ``; +} + +function insertVASTMethodForAPV(targetId, vastXml) { let apvVideoAdParam = { s: targetId }; @@ -194,6 +217,13 @@ function insertVASTMethod(targetId, vastXml) { return script.outerHTML; } +function insertVASTMethodForADGBrowserM(vastXml, marginTop) { + const script = document.createElement(`script`); + script.type = 'text/javascript'; + script.innerHTML = `window.ADGBrowserM.init({vastXml: '${vastXml.replace(/\r?\n/g, '')}', marginTop: '${marginTop}'});`; + return script.outerHTML; +} + /** * * @param ad diff --git a/modules/adglareBidAdapter.js b/modules/adglareBidAdapter.js deleted file mode 100644 index d66a38482ec..00000000000 --- a/modules/adglareBidAdapter.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'adglare'; - -export const spec = { - code: BIDDER_CODE, - isBidRequestValid: function (bid) { - let p = bid.params; - if (typeof p.domain === 'string' && !!p.domain.length && p.zID && !isNaN(p.zID) && p.type == 'banner') return true; - return false; - }, - buildRequests: function (validBidRequests, bidderRequest) { - let i; - let j; - let bidRequest; - let zID; - let domain; - let keywords; - let url; - let type; - let availscreen = window.innerWidth + 'x' + window.innerHeight; - let pixelRatio = window.devicePixelRatio || 1; - let screen = (pixelRatio * window.screen.width) + 'x' + (pixelRatio * window.screen.height); - let sizes = []; - let serverRequests = []; - let timeout = bidderRequest.timeout || 0; - let referer = bidderRequest.refererInfo.referer || ''; - let reachedtop = bidderRequest.refererInfo.reachedTop || ''; - for (i = 0; i < validBidRequests.length; i++) { - bidRequest = validBidRequests[i]; - zID = bidRequest.params.zID; - domain = bidRequest.params.domain; - keywords = bidRequest.params.keywords || ''; - type = bidRequest.params.type; - - // Build ad unit sizes - if (bidRequest.mediaTypes && bidRequest.mediaTypes[type]) { - for (j in bidRequest.mediaTypes[type].sizes) { - sizes.push(bidRequest.mediaTypes[type].sizes[j].join('x')); - } - } - - // Build URL - url = 'https://' + domain + '/?' + encodeURIComponent(zID) + '&pbjs&pbjs_version=1'; - url += '&pbjs_type=' + encodeURIComponent(type); - url += '&pbjs_timeout=' + encodeURIComponent(timeout); - url += '&pbjs_reachedtop=' + encodeURIComponent(reachedtop); - url += '&sizes=' + encodeURIComponent(sizes.join(',')); - url += '&screen=' + encodeURIComponent(screen); - url += '&availscreen=' + encodeURIComponent(availscreen); - url += '&referer=' + encodeURIComponent(referer); - if (keywords !== '') { - url += '&keywords=' + encodeURIComponent(keywords); - } - - // Push server request - serverRequests.push({ - method: 'GET', - url: url, - data: {}, - bidRequest: bidRequest - }); - } - return serverRequests; - }, - interpretResponse: function (serverResponse, request) { - const bidObj = request.bidRequest; - let bidResponses = []; - let bidResponse = {}; - serverResponse = serverResponse.body; - - if (serverResponse && serverResponse.status == 'OK' && bidObj) { - bidResponse.requestId = bidObj.bidId; - bidResponse.bidderCode = bidObj.bidder; - bidResponse.cpm = serverResponse.cpm; - bidResponse.width = serverResponse.width; - bidResponse.height = serverResponse.height; - bidResponse.ad = serverResponse.adhtml; - bidResponse.ttl = serverResponse.ttl; - bidResponse.creativeId = serverResponse.crID; - bidResponse.netRevenue = true; - bidResponse.currency = serverResponse.currency; - bidResponses.push(bidResponse); - } - return bidResponses; - }, -}; -registerBidder(spec); diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js index b30f9cebbc1..6a8c98650c0 100644 --- a/modules/adhashBidAdapter.js +++ b/modules/adhashBidAdapter.js @@ -6,7 +6,7 @@ const VERSION = '1.0'; export const spec = { code: 'adhash', - url: 'https://bidder.adhash.org/rtb?version=' + VERSION + '&prebid=true', + url: 'https://bidder.adhash.com/rtb?version=' + VERSION + '&prebid=true', supportedMediaTypes: [ BANNER ], isBidRequestValid: (bid) => { @@ -37,7 +37,7 @@ export const spec = { var size = validBidRequests[i].sizes[index].join('x'); bidRequests.push({ method: 'POST', - url: url, + url: url + '&publisher=' + validBidRequests[i].params.publisherId, bidRequest: validBidRequests[i], data: { timezone: new Date().getTimezoneOffset() / 60, @@ -87,14 +87,17 @@ export const spec = { cpm: responseBody.creatives[0].costEUR, ad: `
- + `, width: request.bidRequest.sizes[0][0], height: request.bidRequest.sizes[0][1], creativeId: request.bidRequest.adUnitCode, netRevenue: true, currency: 'EUR', - ttl: 60 + ttl: 60, + meta: { + advertiserDomains: responseBody.advertiserDomains ? [responseBody.advertiserDomains] : [] + } }]; } }; diff --git a/modules/adhashBidAdapter.md b/modules/adhashBidAdapter.md index c1093e0ccd7..4ee6ed3dc83 100644 --- a/modules/adhashBidAdapter.md +++ b/modules/adhashBidAdapter.md @@ -34,7 +34,7 @@ Please note that a number of AdHash functionalities are not supported in the Pre bidder: 'adhash', params: { publisherId: '0x1234567890123456789012345678901234567890', - platformURL: 'https://adhash.org/p/struma/' + platformURL: 'https://adhash.org/p/example/' } } ] diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index b9dbae529ba..145b5605bc2 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -2,6 +2,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'adhese'; const GVLID = 553; @@ -20,11 +21,15 @@ export const spec = { if (validBidRequests.length === 0) { return null; } + const { gdprConsent, refererInfo } = bidderRequest; + const adheseConfig = config.getConfig('adhese'); const gdprParams = (gdprConsent && gdprConsent.consentString) ? { xt: [gdprConsent.consentString] } : {}; const refererParams = (refererInfo && refererInfo.referer) ? { xf: [base64urlEncode(refererInfo.referer)] } : {}; - const commonParams = { ...gdprParams, ...refererParams }; + const globalCustomParams = (adheseConfig && adheseConfig.globalTargets) ? cleanTargets(adheseConfig.globalTargets) : {}; + const commonParams = { ...globalCustomParams, ...gdprParams, ...refererParams }; + const vastContentAsUrl = !(adheseConfig && adheseConfig.vastContentAsUrl == false); const slots = validBidRequests.map(bid => ({ slotname: bidToSlotName(bid), @@ -34,7 +39,7 @@ export const spec = { const payload = { slots: slots, parameters: commonParams, - vastContentAsUrl: true, + vastContentAsUrl: vastContentAsUrl, user: { ext: { eids: getEids(validBidRequests), @@ -107,6 +112,9 @@ function adResponse(bid, ad) { originData: adDetails.originData, origin: adDetails.origin, originInstance: adDetails.originInstance + }, + meta: { + advertiserDomains: ad.adomain || [] } }); diff --git a/modules/adkernelAdnAnalyticsAdapter.js b/modules/adkernelAdnAnalyticsAdapter.js index 23501f7dd63..2b4e67736f3 100644 --- a/modules/adkernelAdnAnalyticsAdapter.js +++ b/modules/adkernelAdnAnalyticsAdapter.js @@ -1,7 +1,7 @@ import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -import * as utils from '../src/utils.js'; +import { logError, parseUrl, _each } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {config} from '../src/config.js'; @@ -90,7 +90,7 @@ analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics; analyticsAdapter.enableAnalytics = (config) => { if (!config.options.pubId) { - utils.logError('PubId is not defined. Analytics won\'t work'); + logError('PubId is not defined. Analytics won\'t work'); return; } analyticsAdapter.context = { @@ -215,7 +215,7 @@ export function getUmtSource(pageUrl, referrer) { if (se) { return asUtm(se, ORGANIC, ORGANIC); } - let parsedUrl = utils.parseUrl(pageUrl); + let parsedUrl = parseUrl(pageUrl); let [refHost, refPath] = getReferrer(referrer); if (refHost && refHost !== parsedUrl.hostname) { return asUtm(refHost, REFERRAL, REFERRAL, '', refPath); @@ -242,17 +242,17 @@ export function getUmtSource(pageUrl, referrer) { } function getReferrer(referrer) { - let ref = utils.parseUrl(referrer); + let ref = parseUrl(referrer); return [ref.hostname, ref.pathname]; } function getUTM(pageUrl) { - let urlParameters = utils.parseUrl(pageUrl).search; + let urlParameters = parseUrl(pageUrl).search; if (!urlParameters['utm_campaign'] || !urlParameters['utm_source']) { return; } let utmArgs = []; - utils._each(UTM_TAGS, (utmTagName) => { + _each(UTM_TAGS, (utmTagName) => { let utmValue = urlParameters[utmTagName] || ''; utmArgs.push(utmValue); }); diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js index 0de750c773f..39f7b9fd2b2 100644 --- a/modules/adkernelAdnBidAdapter.js +++ b/modules/adkernelAdnBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { deepAccess, parseSizesInput, isArray, deepSetValue, parseUrl, isStr, isNumber, logInfo } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -19,12 +19,12 @@ function buildImp(bidRequest) { tagid: bidRequest.adUnitCode }; let mediaType; - let bannerReq = utils.deepAccess(bidRequest, `mediaTypes.banner`); - let videoReq = utils.deepAccess(bidRequest, `mediaTypes.video`); + let bannerReq = deepAccess(bidRequest, `mediaTypes.banner`); + let videoReq = deepAccess(bidRequest, `mediaTypes.video`); if (bannerReq) { let sizes = canonicalizeSizesArray(bannerReq.sizes); imp.banner = { - format: utils.parseSizesInput(sizes) + format: parseSizesInput(sizes) }; mediaType = BANNER; } else if (videoReq) { @@ -51,7 +51,7 @@ function buildImp(bidRequest) { * @return Array[Array[Number]] */ function canonicalizeSizesArray(sizes) { - if (sizes.length === 2 && !utils.isArray(sizes[0])) { + if (sizes.length === 2 && !isArray(sizes[0])) { return [sizes]; } return sizes; @@ -67,23 +67,23 @@ function buildRequestParams(tags, bidderRequest) { }; if (gdprConsent) { if (gdprConsent.gdprApplies !== undefined) { - utils.deepSetValue(req, 'user.gdpr', ~~gdprConsent.gdprApplies); + deepSetValue(req, 'user.gdpr', ~~gdprConsent.gdprApplies); } if (gdprConsent.consentString !== undefined) { - utils.deepSetValue(req, 'user.consent', gdprConsent.consentString); + deepSetValue(req, 'user.consent', gdprConsent.consentString); } } if (uspConsent) { - utils.deepSetValue(req, 'user.us_privacy', uspConsent); + deepSetValue(req, 'user.us_privacy', uspConsent); } if (config.getConfig('coppa')) { - utils.deepSetValue(req, 'user.coppa', 1); + deepSetValue(req, 'user.coppa', 1); } return req; } function buildSite(refInfo) { - let loc = utils.parseUrl(refInfo.referer); + let loc = parseUrl(refInfo.referer); let result = { page: `${loc.protocol}://${loc.hostname}${loc.pathname}`, secure: ~~(loc.protocol === 'https') @@ -103,13 +103,17 @@ function buildBid(tag) { requestId: tag.impid, bidderCode: spec.code, cpm: tag.bid, - width: tag.w, - height: tag.h, creativeId: tag.crid, currency: 'USD', ttl: 720, netRevenue: true }; + if (tag.w) { + bid.width = tag.w; + } + if (tag.h) { + bid.height = tag.h; + } if (tag.tag) { bid.ad = tag.tag; bid.mediaType = BANNER; @@ -117,9 +121,31 @@ function buildBid(tag) { bid.vastUrl = tag.vast_url; bid.mediaType = VIDEO; } + fillBidMeta(bid, tag); return bid; } +function fillBidMeta(bid, tag) { + if (isStr(tag.agencyName)) { + deepSetValue(bid, 'meta.agencyName', tag.agencyName); + } + if (isNumber(tag.advertiserId)) { + deepSetValue(bid, 'meta.advertiserId', tag.advertiserId); + } + if (isStr(tag.advertiserName)) { + deepSetValue(bid, 'meta.advertiserName', tag.advertiserName); + } + if (isArray(tag.advertiserDomains)) { + deepSetValue(bid, 'meta.advertiserDomains', tag.advertiserDomains); + } + if (isStr(tag.primaryCatId)) { + deepSetValue(bid, 'meta.primaryCatId', tag.primaryCatId); + } + if (isArray(tag.secondaryCatIds)) { + deepSetValue(bid, 'meta.secondaryCatIds', tag.secondaryCatIds); + } +} + function getBidFloor(bid, mediaType, sizes) { var floor; var size = sizes.length === 1 ? sizes[0] : '*'; @@ -178,7 +204,7 @@ export const spec = { return []; } if (response.debug) { - utils.logInfo(`ADKERNEL DEBUG:\n${response.debug}`); + logInfo(`ADKERNEL DEBUG:\n${response.debug}`); } return response.tags.map(buildBid); }, diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index e4b04a27d42..0dcde7dff91 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -1,4 +1,7 @@ -import * as utils from '../src/utils.js'; +import { + isStr, isArray, isPlainObject, deepSetValue, isNumber, deepAccess, getAdUnitSizes, parseGPTSingleSizeArrayToRtbSize, + cleanObj, contains, getDNT, parseUrl, createTrackPixelHtml, _each, isArrayOfNums, mergeDeep, isEmpty, inIframe +} from '../src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import find from 'core-js-pure/features/array/find.js'; @@ -67,8 +70,16 @@ export const spec = { {code: 'stringads'}, {code: 'bcm'}, {code: 'engageadx'}, - {code: 'converge_digital', gvlid: 248}, - {code: 'adomega'} + {code: 'converge', gvlid: 248}, + {code: 'adomega'}, + {code: 'denakop'}, + {code: 'rtbanalytica'}, + {code: 'unibots'}, + {code: 'catapultx'}, + {code: 'ergadx'}, + {code: 'turktelekom'}, + {code: 'felixads'}, + {code: 'motionspots'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -94,17 +105,16 @@ export const spec = { * @returns {ServerRequest[]} */ buildRequests: function (bidRequests, bidderRequest) { - let impDispatch = dispatchImps(bidRequests, bidderRequest.refererInfo); + let impGroups = groupImpressionsByHostZone(bidRequests, bidderRequest.refererInfo); let requests = []; let schain = bidRequests[0].schain; - Object.keys(impDispatch).forEach(host => { - Object.keys(impDispatch[host]).forEach(zoneId => { - const request = buildRtbRequest(impDispatch[host][zoneId], bidderRequest, schain); - requests.push({ - method: 'POST', - url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`, - data: JSON.stringify(request) - }); + _each(impGroups, impGroup => { + let {host, zoneId, imps} = impGroup; + const request = buildRtbRequest(imps, bidderRequest, schain); + requests.push({ + method: 'POST', + url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`, + data: JSON.stringify(request) }); }); return requests; @@ -151,24 +161,24 @@ export const spec = { prBid.mediaType = NATIVE; prBid.native = buildNativeAd(JSON.parse(rtbBid.adm)); } - if (utils.isStr(rtbBid.dealid)) { + if (isStr(rtbBid.dealid)) { prBid.dealId = rtbBid.dealid; } - if (utils.isArray(rtbBid.adomain)) { - utils.deepSetValue(prBid, 'meta.advertiserDomains', rtbBid.adomain); + if (isArray(rtbBid.adomain)) { + deepSetValue(prBid, 'meta.advertiserDomains', rtbBid.adomain); } - if (utils.isArray(rtbBid.cat)) { - utils.deepSetValue(prBid, 'meta.secondaryCatIds', rtbBid.cat); + if (isArray(rtbBid.cat)) { + deepSetValue(prBid, 'meta.secondaryCatIds', rtbBid.cat); } - if (utils.isPlainObject(rtbBid.ext)) { - if (utils.isNumber(rtbBid.ext.advertiser_id)) { - utils.deepSetValue(prBid, 'meta.advertiserId', rtbBid.ext.advertiser_id); + if (isPlainObject(rtbBid.ext)) { + if (isNumber(rtbBid.ext.advertiser_id)) { + deepSetValue(prBid, 'meta.advertiserId', rtbBid.ext.advertiser_id); } - if (utils.isStr(rtbBid.ext.advertiser_name)) { - utils.deepSetValue(prBid, 'meta.advertiserName', rtbBid.ext.advertiser_name); + if (isStr(rtbBid.ext.advertiser_name)) { + deepSetValue(prBid, 'meta.advertiserName', rtbBid.ext.advertiser_name); } - if (utils.isStr(rtbBid.ext.agency_name)) { - utils.deepSetValue(prBid, 'meta.agencyName', rtbBid.ext.agency_name); + if (isStr(rtbBid.ext.agency_name)) { + deepSetValue(prBid, 'meta.agencyName', rtbBid.ext.agency_name); } } @@ -200,17 +210,19 @@ registerBidder(spec); * @param bidRequests {BidRequest[]} * @param refererInfo {refererInfo} */ -function dispatchImps(bidRequests, refererInfo) { +function groupImpressionsByHostZone(bidRequests, refererInfo) { let secure = (refererInfo && refererInfo.referer.indexOf('https:') === 0); - return bidRequests.map(bidRequest => buildImp(bidRequest, secure)) - .reduce((acc, curr, index) => { - let bidRequest = bidRequests[index]; - let {zoneId, host} = bidRequest.params; - acc[host] = acc[host] || {}; - acc[host][zoneId] = acc[host][zoneId] || []; - acc[host][zoneId].push(curr); - return acc; - }, {}); + return Object.values( + bidRequests.map(bidRequest => buildImp(bidRequest, secure)) + .reduce((acc, curr, index) => { + let bidRequest = bidRequests[index]; + let {zoneId, host} = bidRequest.params; + let key = `${host}_${zoneId}`; + acc[key] = acc[key] || {host: host, zoneId: zoneId, imps: []}; + acc[key].imps.push(curr); + return acc; + }, {}) + ); } function getBidFloor(bid, mediaType, sizes) { @@ -238,19 +250,19 @@ function buildImp(bidRequest, secure) { var mediaType; var sizes = []; - if (utils.deepAccess(bidRequest, 'mediaTypes.banner')) { - sizes = utils.getAdUnitSizes(bidRequest); + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + sizes = getAdUnitSizes(bidRequest); imp.banner = { - format: sizes.map(wh => utils.parseGPTSingleSizeArrayToRtbSize(wh)), + format: sizes.map(wh => parseGPTSingleSizeArrayToRtbSize(wh)), topframe: 0 }; mediaType = BANNER; - } else if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { - let video = utils.deepAccess(bidRequest, 'mediaTypes.video'); + } else if (deepAccess(bidRequest, 'mediaTypes.video')) { + let video = deepAccess(bidRequest, 'mediaTypes.video'); imp.video = {}; if (video.playerSize) { sizes = video.playerSize[0]; - imp.video = Object.assign(imp.video, utils.parseGPTSingleSizeArrayToRtbSize(sizes) || {}); + imp.video = Object.assign(imp.video, parseGPTSingleSizeArrayToRtbSize(sizes) || {}); } if (bidRequest.params.video) { Object.keys(bidRequest.params.video) @@ -258,7 +270,7 @@ function buildImp(bidRequest, secure) { .forEach(key => imp.video[key] = bidRequest.params.video[key]); } mediaType = VIDEO; - } else if (utils.deepAccess(bidRequest, 'mediaTypes.native')) { + } else if (deepAccess(bidRequest, 'mediaTypes.native')) { let nativeRequest = buildNativeRequest(bidRequest.mediaTypes.native); imp.native = { ver: '1.1', @@ -296,7 +308,7 @@ function buildNativeRequest(nativeReq) { if (desc.assetType === 'img') { assetRoot[desc.assetType] = buildImageAsset(desc, v); } else if (desc.assetType === 'data') { - assetRoot.data = utils.cleanObj({type: desc.type, len: v.len}); + assetRoot.data = cleanObj({type: desc.type, len: v.len}); } else if (desc.assetType === 'title') { assetRoot.title = {len: v.len || 90}; } else { @@ -320,7 +332,7 @@ function buildImageAsset(desc, val) { img.wmin = val.aspect_ratios[0].min_width; img.hmin = val.aspect_ratios[0].min_height; } - return utils.cleanObj(img); + return cleanObj(img); } /** @@ -333,9 +345,9 @@ function isSyncMethodAllowed(syncRule, bidderCode) { if (!syncRule) { return false; } - let bidders = utils.isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + let bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; let rule = syncRule.filter === 'include'; - return utils.contains(bidders, bidderCode) === rule; + return contains(bidders, bidderCode) === rule; } /** @@ -356,52 +368,141 @@ function getAllowedSyncMethod(bidderCode) { } /** - * Builds complete rtb request - * @param imps {Object} Collection of rtb impressions + * Create device object from fpd and host-collected data + * @param fpd {Object} + * @returns {{device: Object}} + */ +function makeDevice(fpd) { + let device = mergeDeep({ + 'ip': 'caller', + 'ipv6': 'caller', + 'ua': 'caller', + 'js': 1, + 'language': getLanguage() + }, fpd.device || {}); + if (getDNT()) { + device.dnt = 1; + } + return {device: device}; +} + +/** + * Create site or app description object * @param bidderRequest {BidderRequest} - * @param schain {Object=} Supply chain config - * @return {Object} Complete rtb request + * @param fpd {Object} + * @returns {{site: Object}|{app: Object}} */ -function buildRtbRequest(imps, bidderRequest, schain) { - let {bidderCode, gdprConsent, auctionId, refererInfo, timeout, uspConsent} = bidderRequest; - let coppa = config.getConfig('coppa'); - let req = { - 'id': auctionId, - 'imp': imps, - 'site': createSite(refererInfo), - 'at': 1, - 'device': { - 'ip': 'caller', - 'ipv6': 'caller', - 'ua': 'caller', - 'js': 1, - 'language': getLanguage() - }, - 'tmax': parseInt(timeout) - }; - if (utils.getDNT()) { - req.device.dnt = 1; +function makeSiteOrApp(bidderRequest, fpd) { + let {refererInfo} = bidderRequest; + let appConfig = config.getConfig('app'); + if (isEmpty(appConfig)) { + return {site: createSite(refererInfo, fpd)} + } else { + return {app: appConfig}; } +} + +/** + * Create user description object + * @param bidderRequest {BidderRequest} + * @param fpd {Object} + * @returns {{user: Object} | undefined} + */ +function makeUser(bidderRequest, fpd) { + let {gdprConsent} = bidderRequest; + let user = fpd.user || {}; + if (gdprConsent && gdprConsent.consentString !== undefined) { + deepSetValue(user, 'ext.consent', gdprConsent.consentString); + } + let eids = getExtendedUserIds(bidderRequest); + if (eids) { + deepSetValue(user, 'ext.eids', eids); + } + if (!isEmpty(user)) { return {user: user}; } +} + +/** + * Create privacy regulations object + * @param bidderRequest {BidderRequest} + * @returns {{regs: Object} | undefined} + */ +function makeRegulations(bidderRequest) { + let {gdprConsent, uspConsent} = bidderRequest; + let regs = {}; if (gdprConsent) { if (gdprConsent.gdprApplies !== undefined) { - utils.deepSetValue(req, 'regs.ext.gdpr', ~~gdprConsent.gdprApplies); - } - if (gdprConsent.consentString !== undefined) { - utils.deepSetValue(req, 'user.ext.consent', gdprConsent.consentString); + deepSetValue(regs, 'regs.ext.gdpr', ~~gdprConsent.gdprApplies); } } if (uspConsent) { - utils.deepSetValue(req, 'regs.ext.us_privacy', uspConsent); + deepSetValue(regs, 'regs.ext.us_privacy', uspConsent); + } + if (config.getConfig('coppa')) { + deepSetValue(regs, 'regs.coppa', 1); + } + if (!isEmpty(regs)) { + return regs; + } +} + +/** + * Create top-level request object + * @param bidderRequest {BidderRequest} + * @param imps {Object} Impressions + * @param fpd {Object} First party data + * @returns + */ +function makeBaseRequest(bidderRequest, imps, fpd) { + let {auctionId, timeout} = bidderRequest; + let request = { + 'id': auctionId, + 'imp': imps, + 'at': 1, + 'tmax': parseInt(timeout) + }; + if (!isEmpty(fpd.bcat)) { + request.bcat = fpd.bcat; } - if (coppa) { - utils.deepSetValue(req, 'regs.coppa', 1); + if (!isEmpty(fpd.badv)) { + request.badv = fpd.badv; } + return request; +} + +/** + * Initialize sync capabilities + * @param bidderRequest {BidderRequest} + */ +function makeSyncInfo(bidderRequest) { + let {bidderCode} = bidderRequest; let syncMethod = getAllowedSyncMethod(bidderCode); if (syncMethod) { - utils.deepSetValue(req, 'ext.adk_usersync', syncMethod); + let res = {}; + deepSetValue(res, 'ext.adk_usersync', syncMethod); + return res; } +} + +/** + * Builds complete rtb request + * @param imps {Object} Collection of rtb impressions + * @param bidderRequest {BidderRequest} + * @param schain {Object=} Supply chain config + * @return {Object} Complete rtb request + */ +function buildRtbRequest(imps, bidderRequest, schain) { + let fpd = config.getConfig('ortb2') || {}; + + let req = mergeDeep( + makeBaseRequest(bidderRequest, imps, fpd), + makeDevice(fpd), + makeSiteOrApp(bidderRequest, fpd), + makeUser(bidderRequest, fpd), + makeRegulations(bidderRequest), + makeSyncInfo(bidderRequest) + ); if (schain) { - utils.deepSetValue(req, 'source.ext.schain', schain); + deepSetValue(req, 'source.ext.schain', schain); } return req; } @@ -418,22 +519,28 @@ function getLanguage() { /** * Creates site description object */ -function createSite(refInfo) { - let url = utils.parseUrl(refInfo.referer); +function createSite(refInfo, fpd) { + let url = parseUrl(refInfo.referer); let site = { 'domain': url.hostname, 'page': `${url.protocol}://${url.hostname}${url.pathname}` }; - if (self === top && document.referrer) { + mergeDeep(site, fpd.site); + if (!inIframe() && document.referrer) { site.ref = document.referrer; - } - let keywords = document.getElementsByTagName('meta')['keywords']; - if (keywords && keywords.content) { - site.keywords = keywords.content; + } else { + delete site.ref; } return site; } +function getExtendedUserIds(bidderRequest) { + let eids = deepAccess(bidderRequest, 'bids.0.userIdAsEids'); + if (isArray(eids)) { + return eids; + } +} + /** * Format creative with optional nurl call * @param bid rtb Bid object @@ -441,7 +548,7 @@ function createSite(refInfo) { function formatAdMarkup(bid) { let adm = bid.adm; if ('nurl' in bid) { - adm += utils.createTrackPixelHtml(`${bid.nurl}&px=1`); + adm += createTrackPixelHtml(`${bid.nurl}&px=1`); } return adm; } @@ -451,8 +558,8 @@ function formatAdMarkup(bid) { */ function validateNativeAdUnit(adUnit) { return validateNativeImageSize(adUnit.image) && validateNativeImageSize(adUnit.icon) && - !utils.deepAccess(adUnit, 'privacyLink.required') && // not supported yet - !utils.deepAccess(adUnit, 'privacyIcon.required'); // not supported yet + !deepAccess(adUnit, 'privacyLink.required') && // not supported yet + !deepAccess(adUnit, 'privacyIcon.required'); // not supported yet } /** @@ -463,9 +570,9 @@ function validateNativeImageSize(img) { return true; } if (img.sizes) { - return utils.isArrayOfNums(img.sizes, 2); + return isArrayOfNums(img.sizes, 2); } - if (utils.isArray(img.aspect_ratios)) { + if (isArray(img.aspect_ratios)) { return img.aspect_ratios.length > 0 && img.aspect_ratios[0].min_height && img.aspect_ratios[0].min_width; } return true; @@ -482,14 +589,14 @@ function buildNativeAd(nativeResp) { javascriptTrackers: jstracker ? [jstracker] : undefined, privacyLink: privacy, }; - utils._each(assets, asset => { + _each(assets, asset => { let assetName = NATIVE_MODEL[asset.id].name; let assetType = NATIVE_MODEL[asset.id].assetType; - nativeAd[assetName] = asset[assetType].text || asset[assetType].value || utils.cleanObj({ + nativeAd[assetName] = asset[assetType].text || asset[assetType].value || cleanObj({ url: asset[assetType].url, width: asset[assetType].w, height: asset[assetType].h }); }); - return utils.cleanObj(nativeAd); + return cleanObj(nativeAd); } diff --git a/modules/adliveBidAdapter.js b/modules/adliveBidAdapter.js deleted file mode 100644 index 4c703628c4d..00000000000 --- a/modules/adliveBidAdapter.js +++ /dev/null @@ -1,69 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'adlive'; -const ENDPOINT_URL = 'https://api.publishers.adlive.io/get?pbjs=1'; - -const CURRENCY = 'USD'; -const TIME_TO_LIVE = 360; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - - isBidRequestValid: function(bid) { - return !!(bid.params.hashes && utils.isArray(bid.params.hashes)); - }, - - buildRequests: function(validBidRequests) { - let requests = []; - - utils._each(validBidRequests, function(bid) { - requests.push({ - method: 'POST', - url: ENDPOINT_URL, - options: { - contentType: 'application/json', - withCredentials: true - }, - data: JSON.stringify({ - transaction_id: bid.bidId, - hashes: utils.getBidIdParameter('hashes', bid.params) - }), - bidId: bid.bidId - }); - }); - - return requests; - }, - - interpretResponse: function(serverResponse, bidRequest) { - try { - const response = serverResponse.body; - const bidResponses = []; - - utils._each(response, function(bidResponse) { - if (!bidResponse.is_passback) { - bidResponses.push({ - requestId: bidRequest.bidId, - cpm: bidResponse.price, - width: bidResponse.size[0], - height: bidResponse.size[1], - creativeId: bidResponse.hash, - currency: CURRENCY, - netRevenue: false, - ttl: TIME_TO_LIVE, - ad: bidResponse.content - }); - } - }); - - return bidResponses; - } catch (err) { - utils.logError(err); - return []; - } - } -}; -registerBidder(spec); diff --git a/modules/adlivetechBidAdapter.md b/modules/adlivetechBidAdapter.md new file mode 100644 index 00000000000..612e669ea1a --- /dev/null +++ b/modules/adlivetechBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +Module Name: Adlivetech Bidder Adapter +Module Type: Bidder Adapter +Maintainer: grid-tech@themediagrid.com + +# Description + +Module that connects to Grid demand source to fetch bids. +The adapter is GDPR compliant and supports banner and video (instream and outstream). + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "adlivetech", + params: { + uid: '1', + bidFloor: 0.5 + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "adlivetech", + params: { + uid: 2, + keywords: { + brandsafety: ['disaster'], + topic: ['stress', 'fear'] + } + } + } + ] + }, + { + code: 'test-div', + sizes: [[728, 90]], + mediaTypes: { video: { + context: 'instream', + playerSize: [728, 90], + mimes: ['video/mp4'] + }, + bids: [ + { + bidder: "adlivetech", + params: { + uid: 11 + } + } + ] + } + ]; +``` diff --git a/modules/adlooxAdServerVideo.js b/modules/adlooxAdServerVideo.js new file mode 100644 index 00000000000..bd715cb34f3 --- /dev/null +++ b/modules/adlooxAdServerVideo.js @@ -0,0 +1,223 @@ +/** + * This module adds [Adloox]{@link https://www.adloox.com/} Ad Server support for Video to Prebid + * @module modules/adlooxAdServerVideo + * @requires module:modules/adlooxAnalyticsAdapter + */ + +/* eslint prebid/validate-imports: "off" */ + +import { registerVideoSupport } from '../src/adServerManager.js'; +import { command as analyticsCommand, COMMAND } from './adlooxAnalyticsAdapter.js'; +import { ajax } from '../src/ajax.js'; +import CONSTANTS from '../src/constants.json'; +import { targeting } from '../src/targeting.js'; +import { logInfo, isFn, logError, isPlainObject, isStr, isBoolean, deepSetValue, deepClone, timestamp, logWarn } from '../src/utils.js'; + +const MODULE = 'adlooxAdserverVideo'; + +const URL_VAST = 'https://j.adlooxtracking.com/ads/vast/tag.php'; + +export function buildVideoUrl(options, callback) { + logInfo(MODULE, 'buildVideoUrl', options); + + if (!isFn(callback)) { + logError(MODULE, 'invalid callback'); + return false; + } + if (!isPlainObject(options)) { + logError(MODULE, 'missing options'); + return false; + } + if (!(options.url_vast === undefined || isStr(options.url_vast))) { + logError(MODULE, 'invalid url_vast options value'); + return false; + } + if (!(isPlainObject(options.adUnit) || isPlainObject(options.bid))) { + logError(MODULE, "requires either 'adUnit' or 'bid' options value"); + return false; + } + if (!isStr(options.url)) { + logError(MODULE, 'invalid url options value'); + return false; + } + if (!(options.wrap === undefined || isBoolean(options.wrap))) { + logError(MODULE, 'invalid wrap options value'); + return false; + } + if (!(options.blob === undefined || isBoolean(options.blob))) { + logError(MODULE, 'invalid blob options value'); + return false; + } + + // same logic used in modules/dfpAdServerVideo.js + options.bid = options.bid || targeting.getWinningBids(options.adUnit.code)[0]; + + deepSetValue(options.bid, 'ext.adloox.video.adserver', true); + + if (options.wrap !== false) { + VASTWrapper(options, callback); + } else { + track(options, callback); + } + + return true; +} + +registerVideoSupport('adloox', { + buildVideoUrl: buildVideoUrl +}); + +function track(options, callback) { + callback(options.url); + + const bid = deepClone(options.bid); + bid.ext.adloox.video.adserver = false; + + analyticsCommand(COMMAND.TRACK, { + eventType: CONSTANTS.EVENTS.BID_WON, + args: bid + }); +} + +function VASTWrapper(options, callback) { + const chain = []; + + function process(result) { + function getAd(xml) { + if (!xml || xml.documentElement.tagName != 'VAST') { + logError(MODULE, 'not a VAST tag, using non-wrapped tracking'); + return; + } + + const ads = xml.querySelectorAll('Ad'); + if (!ads.length) { + logError(MODULE, 'no VAST ads, using non-wrapped tracking'); + return; + } + + // get first Ad (VAST may be an Ad Pod so sort on sequence and pick lowest sequence number) + const ad = Array.prototype.slice.call(ads).sort(function(a, b) { + return parseInt(a.getAttribute('sequence'), 10) - parseInt(b.getAttribute('sequence'), 10); + }).shift(); + + return ad; + } + + function getWrapper(ad) { + return ad.querySelector('VASTAdTagURI'); + } + + function durationToSeconds(duration) { + return Date.parse('1970-01-01 ' + duration + 'Z') / 1000; + } + + function blobify() { + if (!(chain.length > 0 && options.blob !== false)) return; + + const urls = []; + + function toBlob(r) { + const text = new XMLSerializer().serializeToString(r.xml); + const url = URL.createObjectURL(new Blob([text], { type: r.type })); + urls.push(url); + return url; + } + + let n = chain.length - 1; // do not process the linear + while (n-- > 0) { + const ad = getAd(chain[n].xml); + const wrapper = getWrapper(ad); + wrapper.textContent = toBlob(chain[n + 1]); + } + + options.url = toBlob(chain[0]); + + const epoch = timestamp() - new Date().getTimezoneOffset() * 60 * 1000; + const expires0 = options.bid.ttl * 1000 - (epoch - options.bid.responseTimestamp); + const expires = Math.max(30 * 1000, expires0); + setTimeout(function() { urls.forEach(u => URL.revokeObjectURL(u)) }, expires); + } + + if (!result) { + blobify(); + return track(options, callback); + } + + const ad = getAd(result.xml); + if (!ad) { + blobify(); + return track(options, callback); + } + + chain.push(result); + + const wrapper = getWrapper(ad); + if (wrapper) { + if (chain.length > 5) { + logWarn(MODULE, `got wrapped tag at depth ${chain.length}, not continuing`); + blobify(); + return track(options, callback); + } + return fetch(wrapper.textContent.trim()); + } + + blobify(); + + const version = chain[0].xml.documentElement.getAttribute('version'); + + const vpaid = ad.querySelector("MediaFiles > MediaFile[apiFramework='VPAID'][type='application/javascript']"); + + const duration = durationToSeconds(ad.querySelector('Duration').textContent.trim()); + + let skip; + const skipd = ad.querySelector('Linear').getAttribute('skipoffset'); + if (skipd) skip = durationToSeconds(skipd.trim()); + + const args = [ + [ 'client', '%%client%%' ], + [ 'platform_id', '%%platformid%%' ], + [ 'scriptname', 'adl_%%clientid%%' ], + [ 'tag_id', '%%tagid%%' ], + [ 'fwtype', 4 ], + [ 'vast', options.url ], + [ 'id11', 'video' ], + [ 'id12', '$ADLOOX_WEBSITE' ], + [ 'id18', (!skip || skip >= duration) ? 'fd' : 'od' ], + [ 'id19', 'na' ], + [ 'id20', 'na' ] + ]; + if (version && version != 3) args.push([ 'version', version ]); + if (vpaid) args.push([ 'vpaid', 1 ]); + if (duration != 15) args.push([ 'duration', duration ]); + if (skip) args.push([ 'skip', skip ]); + + logInfo(MODULE, `processed VAST tag chain of depth ${chain.depth}, running callback`); + + analyticsCommand(COMMAND.URL, { + url: (options.url_vast || URL_VAST) + '?', + args: args, + bid: options.bid, + ids: true + }, callback); + } + + function fetch(url, withoutcredentials) { + logInfo(MODULE, `fetching VAST ${url}`); + + ajax(url, { + success: function(responseText, q) { + process({ type: q.getResponseHeader('content-type'), xml: q.responseXML }); + }, + error: function(statusText, q) { + if (!withoutcredentials) { + logWarn(MODULE, `unable to download (${statusText}), suspected CORS withCredentials problem, retrying without`); + return fetch(url, true); + } + logError(MODULE, `failed to fetch (${statusText}), using non-wrapped tracking`); + process(); + } + }, undefined, { withCredentials: !withoutcredentials }); + } + + fetch(options.url); +} diff --git a/modules/adlooxAdServerVideo.md b/modules/adlooxAdServerVideo.md new file mode 100644 index 00000000000..db8e3cfb295 --- /dev/null +++ b/modules/adlooxAdServerVideo.md @@ -0,0 +1,92 @@ +# Overview + + Module Name: Adloox Ad Server Video + Module Type: Ad Server Video + Maintainer: contact@adloox.com + +# Description + +Ad Server Video for adloox.com. Contact contact@adloox.com for information. + +This module pairs with the [Adloox Analytics Adapter](./adlooxAnalyticsAdapter.md) to provide video tracking and measurement. + +Prebid.js does not support [`bidWon` events for video](https://github.com/prebid/prebid.github.io/issues/1320) without the [Instream Video Ads Tracking](https://docs.prebid.org/dev-docs/modules/instreamTracking.html) module that has the limitations: + + * works only with instream video + * viewability metrics are *not* [MRC accredited](http://mediaratingcouncil.org/) + +This module has two modes of operation configurable with the `wrap` parameter below: + + * **`true` [default]:** + * provides MRC accredited viewability measurement of your [IAB](https://www.iab.com/) [VPAID](https://iabtechlab.com/standards/video-player-ad-interface-definition-vpaid/) and [OM SDK](https://iabtechlab.com/standards/open-measurement-sdk/) enabled inventory + * VAST tracking is collected by Adloox + * wraps the winning bid VAST URL with the Adloox VAST/VPAID wrapper (`https://j.adlooxtracking.com/ads/vast/tag.php?...`) + * **`false`:** + * sends a `bidWon` event *only* to the Adloox Analytics Adapter + * inventory is measured + * viewability metrics are *not* MRC accredited. + * VAST tracking is *not* collected by Adloox + +**N.B.** this module is compatible for use alongside the Instream Video Ads Tracking module though not required in order to function + +## Example + +To view an example of an Adloox integration look at the example provided in the [Adloox Analytics Adapter documentation](./adlooxAnalyticsAdapter.md#example). + +# Integration + +To use this, you *must* also integrate the [Adloox Analytics Adapter](./adlooxAnalyticsAdapter.md) (and optionally the Instream Video Ads Tracking module) as shown below: + + function sendAdserverRequest(bids, timedOut, auctionId) { + if (pbjs.initAdserverSet) return; + pbjs.initAdserverSet = true; + + // handle display tags as usual + googletag.cmd.push(function() { + pbjs.setTargetingForGPTAsync && pbjs.setTargetingForGPTAsync(adUnits); + googletag.pubads().refresh(); + }); + + // handle the bids on the video adUnit + var videoBids = bids[videoAdUnit.code]; + if (videoBids) { + var videoUrl = pbjs.adServers.dfp.buildVideoUrl({ + adUnit: videoAdUnit, + params: { + iu: '/19968336/prebid_cache_video_adunit', + cust_params: { + section: 'blog', + anotherKey: 'anotherValue' + }, + output: 'vast' + } + }); + pbjs.adServers.adloox.buildVideoUrl({ + adUnit: videoAdUnit, + url: videoUrl + }, invokeVideoPlayer); + } + } + +The helper function takes the form: + + pbjs.adServers.adloox.buildVideoUrl(options, callback) + +Where: + + * **`options`:** configuration object: + * **`adUnit`:** ad unit that is being filled + * **`bid` [optional]:** if you override the hardcoded `pbjs.adServers.dfp.buildVideoUrl(...)` logic that picks the first bid you *must* pass in the `bid` object you select + * **`url`:** VAST tag URL, typically the value returned by `pbjs.adServers.dfp.buildVideoUrl(...)` + * **`wrap`:** + * **`true` [default]:** VAST tag is be converted to an Adloox VAST wrapped tag + * **`false`:** VAST tag URL is returned as is + * **`blob`:** + * **`true` [default]:** [Blob URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL) is returned so that the VAST tag is only fetched once + * only has an affect when `wrap` is set to `true` + * **`false`:** VAST tag may be fetched twice depending on your Ad Server and video player configuration + * use when the ad is served into a cross-origin (non-friendly) IFRAME + * use if during QAing you discover your video player does not supports Blob URLs; widely supported (including JW Player) so contact your player vendor to resolve this where possible for the best user and device experience + * **`callback`:** function you use to pass the VAST tag URL to your video player + +**N.B.** call `pbjs.adServers.adloox.buildVideoUrl(...)` as close as possible to starting the ad to reduce impression discrepancies diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 3e92ae34004..781b8db830a 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -9,9 +9,12 @@ import adapter from '../src/AnalyticsAdapter.js'; import { loadExternalScript } from '../src/adloader.js'; import { auctionManager } from '../src/auctionManager.js'; import { AUCTION_COMPLETED } from '../src/auction.js'; -import { EVENTS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; import find from 'core-js-pure/features/array/find.js'; -import * as utils from '../src/utils.js'; +import { + deepAccess, logInfo, isPlainObject, logError, isStr, isNumber, getGptSlotInfoForAdUnitCode, + isFn, mergeDeep, logMessage, insertElement, logWarn, getUniqueIdentifierStr, parseUrl +} from '../src/utils.js'; const MODULE = 'adlooxAnalyticsAdapter'; @@ -43,14 +46,15 @@ MACRO['targetelt'] = function(b, c) { MACRO['creatype'] = function(b, c) { return b.mediaType == 'video' ? ADLOOX_MEDIATYPE.VIDEO : ADLOOX_MEDIATYPE.DISPLAY; }; -MACRO['pbAdSlot'] = function(b, c) { +MACRO['pbadslot'] = function(b, c) { const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); - return utils.deepAccess(adUnit, 'fpd.context.pbAdSlot') || utils.getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; + return deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; }; +MACRO['pbAdSlot'] = MACRO['pbadslot']; // legacy const PARAMS_DEFAULT = { 'id1': function(b) { return b.adUnitCode }, - 'id2': '%%pbAdSlot%%', + 'id2': '%%pbadslot%%', 'id3': function(b) { return b.bidder }, 'id4': function(b) { return b.adId }, 'id5': function(b) { return b.dealId }, @@ -65,7 +69,7 @@ let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { track({ eventType, args }) { if (!analyticsAdapter[`handle_${eventType}`]) return; - utils.logInfo(MODULE, 'track', eventType, args); + logInfo(MODULE, 'track', eventType, args); analyticsAdapter[`handle_${eventType}`](args); } @@ -77,45 +81,45 @@ analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics; analyticsAdapter.enableAnalytics = function(config) { analyticsAdapter.context = null; - utils.logInfo(MODULE, 'config', config); + logInfo(MODULE, 'config', config); - if (!utils.isPlainObject(config.options)) { - utils.logError(MODULE, 'missing options'); + if (!isPlainObject(config.options)) { + logError(MODULE, 'missing options'); return; } - if (!(config.options.js === undefined || utils.isStr(config.options.js))) { - utils.logError(MODULE, 'invalid js options value'); + if (!(config.options.js === undefined || isStr(config.options.js))) { + logError(MODULE, 'invalid js options value'); return; } - if (!(config.options.toselector === undefined || utils.isFn(config.options.toselector))) { - utils.logError(MODULE, 'invalid toselector options value'); + if (!(config.options.toselector === undefined || isFn(config.options.toselector))) { + logError(MODULE, 'invalid toselector options value'); return; } - if (!utils.isStr(config.options.client)) { - utils.logError(MODULE, 'invalid client options value'); + if (!isStr(config.options.client)) { + logError(MODULE, 'invalid client options value'); return; } - if (!utils.isNumber(config.options.clientid)) { - utils.logError(MODULE, 'invalid clientid options value'); + if (!isNumber(config.options.clientid)) { + logError(MODULE, 'invalid clientid options value'); return; } - if (!utils.isNumber(config.options.tagid)) { - utils.logError(MODULE, 'invalid tagid options value'); + if (!isNumber(config.options.tagid)) { + logError(MODULE, 'invalid tagid options value'); return; } - if (!utils.isNumber(config.options.platformid)) { - utils.logError(MODULE, 'invalid platformid options value'); + if (!isNumber(config.options.platformid)) { + logError(MODULE, 'invalid platformid options value'); return; } - if (!(config.options.params === undefined || utils.isPlainObject(config.options.params))) { - utils.logError(MODULE, 'invalid params options value'); + if (!(config.options.params === undefined || isPlainObject(config.options.params))) { + logError(MODULE, 'invalid params options value'); return; } analyticsAdapter.context = { js: config.options.js || URL_JS, toselector: config.options.toselector || function(bid) { - let code = utils.getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode; + let code = getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode; // https://mathiasbynens.be/notes/css-escapes code = code.replace(/^\d/, '\\3$& '); return `#${code}` @@ -127,7 +131,7 @@ analyticsAdapter.enableAnalytics = function(config) { params: [] }; - config.options.params = utils.mergeDeep({}, PARAMS_DEFAULT, config.options.params || {}); + config.options.params = mergeDeep({}, PARAMS_DEFAULT, config.options.params || {}); Object .keys(config.options.params) .forEach(k => { @@ -179,15 +183,15 @@ analyticsAdapter.url = function(url, args, bid) { let n = args.length; while (n-- > 0) { - if (utils.isFn(args[n][1])) { + if (isFn(args[n][1])) { try { args[n][1] = args[n][1](bid); } catch (_) { - utils.logError(MODULE, 'macro', args[n][0], _.message); + logError(MODULE, 'macro', args[n][0], _.message); args[n][1] = `ERROR: ${_.message}`; } } - if (utils.isStr(args[n][1])) { + if (isStr(args[n][1])) { args[n][1] = macros(args[n][1]); } } @@ -195,33 +199,38 @@ analyticsAdapter.url = function(url, args, bid) { return url + a2qs(args); } -analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { +analyticsAdapter[`handle_${CONSTANTS.EVENTS.AUCTION_END}`] = function(auctionDetails) { if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return; - analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP; + analyticsAdapter[`handle_${CONSTANTS.EVENTS.AUCTION_END}`] = NOOP; - utils.logMessage(MODULE, 'preloading verification JS'); + logMessage(MODULE, 'preloading verification JS'); - const uri = utils.parseUrl(analyticsAdapter.url(`${analyticsAdapter.context.js}#`)); + const uri = parseUrl(analyticsAdapter.url(`${analyticsAdapter.context.js}#`)); const link = document.createElement('link'); link.setAttribute('href', `${uri.protocol}://${uri.host}${uri.pathname}`); link.setAttribute('rel', 'preload'); link.setAttribute('as', 'script'); - utils.insertElement(link); + insertElement(link); } -analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { +analyticsAdapter[`handle_${CONSTANTS.EVENTS.BID_WON}`] = function(bid) { + if (deepAccess(bid, 'ext.adloox.video.adserver')) { + logMessage(MODULE, `measuring '${bid.mediaType}' ad unit code '${bid.adUnitCode}' via Ad Server module`); + return; + } + const sl = analyticsAdapter.context.toselector(bid); let el; try { el = document.querySelector(sl); } catch (_) { } if (!el) { - utils.logWarn(MODULE, `unable to find ad unit code '${bid.adUnitCode}' slot using selector '${sl}' (use options.toselector to change), ignoring`); + logWarn(MODULE, `unable to find ad unit code '${bid.adUnitCode}' slot using selector '${sl}' (use options.toselector to change), ignoring`); return; } - utils.logMessage(MODULE, `measuring '${bid.mediaType}' unit at '${bid.adUnitCode}'`); + logMessage(MODULE, `measuring '${bid.mediaType}' unit at '${bid.adUnitCode}'`); const params = analyticsAdapter.context.params.concat([ [ 'tagid', '%%tagid%%' ], @@ -246,11 +255,12 @@ export default analyticsAdapter; const COMMAND_QUEUE = {}; export const COMMAND = { CONFIG: 'config', + TOSELECTOR: 'toselector', URL: 'url', TRACK: 'track' }; export function command(cmd, data, callback0) { - const cid = utils.getUniqueIdentifierStr(); + const cid = getUniqueIdentifierStr(); const callback = function() { delete COMMAND_QUEUE[cid]; if (callback0) callback0.apply(null, arguments); @@ -261,7 +271,7 @@ export function command(cmd, data, callback0) { function commandProcess(cid) { const { cmd, data, callback } = COMMAND_QUEUE[cid]; - utils.logInfo(MODULE, 'command', cmd, data); + logInfo(MODULE, 'command', cmd, data); switch (cmd) { case COMMAND.CONFIG: @@ -273,6 +283,9 @@ function commandProcess(cid) { }; callback(response); break; + case COMMAND.TOSELECTOR: + callback(analyticsAdapter.context.toselector(data.bid)); + break; case COMMAND.URL: if (data.ids) data.args = data.args.concat(analyticsAdapter.context.params.filter(p => /^id([1-9]|10)$/.test(p[0]))); // not >10 callback(analyticsAdapter.url(data.url, data.args, data.bid)); @@ -282,7 +295,7 @@ function commandProcess(cid) { callback(); // drain queue break; default: - utils.logWarn(MODULE, 'command unknown', cmd); + logWarn(MODULE, 'command unknown', cmd); // do not callback as arguments are unknown and to aid debugging } } diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md index 0ca67f937f6..c4618a2e3aa 100644 --- a/modules/adlooxAnalyticsAdapter.md +++ b/modules/adlooxAnalyticsAdapter.md @@ -2,11 +2,11 @@ Module Name: Adloox Analytics Adapter Module Type: Analytics Adapter - Maintainer: technique@adloox.com + Maintainer: contact@adloox.com # Description -Analytics adapter for adloox.com. Contact adops@adloox.com for information. +Analytics adapter for adloox.com. Contact contact@adloox.com for information. This module can be used to track: @@ -18,17 +18,23 @@ The adapter adds an HTML `' + res.tag, + mediaType: res.mediaType.name || 'banner', + meta: { + advertiserDomains: res.adDomains && res.adDomains.length ? res.adDomains : [], + mediaType: res.mediaType.name || 'banner', + } + }; + bidResponses.push(bidResponse); + logInfo('bidResponses', bidResponses); + + return bidResponses; + }, + + /** + * @param {TimedOutBid} timeoutData + */ + onTimeout: (timeoutData) => { + if (timeoutData == null) { + return; + } + logInfo('onTimeout ', timeoutData); + let params = { + bidder: timeoutData.bidder, + bId: timeoutData.bidId, + adUnitCode: timeoutData.adUnitCode, + timeout: timeoutData.timeout, + auctionId: timeoutData.auctionId, + }; + let adqueryRequestUrl = buildUrl({ + protocol: ADQUERY_BIDDER_DOMAIN_PROTOCOL, + hostname: ADQUERY_BIDDER_DOMAIN, + pathname: '/prebid/eventTimeout', + search: params + }); + triggerPixel(adqueryRequestUrl); + }, + + /** + * @param {Bid} bid + */ + onBidWon: (bid) => { + logInfo('onBidWon', bid); + const bidString = JSON.stringify(bid); + const encodedBuf = window.btoa(bidString); + + let params = { + q: encodedBuf, + }; + let adqueryRequestUrl = buildUrl({ + protocol: ADQUERY_BIDDER_DOMAIN_PROTOCOL, + hostname: ADQUERY_BIDDER_DOMAIN, + pathname: '/prebid/eventBidWon', + search: params + }); + triggerPixel(adqueryRequestUrl); + }, + + /** + * @param {Bid} bid + */ + onSetTargeting: (bid) => { + logInfo('onSetTargeting', bid); + + let params = { + bidder: bid.bidder, + width: bid.width, + height: bid.height, + bid: bid.adId, + mediaType: bid.mediaType, + cpm: bid.cpm, + requestId: bid.requestId, + adUnitCode: bid.adUnitCode + }; + + let adqueryRequestUrl = buildUrl({ + protocol: ADQUERY_BIDDER_DOMAIN_PROTOCOL, + hostname: ADQUERY_BIDDER_DOMAIN, + pathname: '/prebid/eventSetTargeting', + search: params + }); + triggerPixel(adqueryRequestUrl); + }, + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncUrl = ADQUERY_USER_SYNC_DOMAIN; + 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}`; + } + return [{ + type: 'image', + url: syncUrl + }]; + } + +}; +function buildRequest(validBidRequests, bidderRequest) { + let qid = Math.random().toString(36).substring(2) + Date.now().toString(36); + let bid = validBidRequests; + + if (storage.getDataFromLocalStorage('qid')) { + qid = storage.getDataFromLocalStorage('qid'); + } else { + storage.setDataInLocalStorage('qid', qid); + } + + return { + placementCode: bid.params.placementId, + auctionId: bid.auctionId, + qid: qid, + type: bid.params.type, + adUnitCode: bid.adUnitCode, + bidId: bid.bidId, + bidder: bid.bidder, + bidderRequestId: bid.bidderRequestId, + bidRequestsCount: bid.bidRequestsCount, + bidderRequestsCount: bid.bidderRequestsCount, + }; +} + +registerBidder(spec); diff --git a/modules/adqueryBidAdapter.md b/modules/adqueryBidAdapter.md new file mode 100644 index 00000000000..e8b68e30406 --- /dev/null +++ b/modules/adqueryBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: Adquery Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@adquery.com + +# Description + +Module that connects to Adquery's demand sources. + +# Test Parameters +``` + var adUnits = [ + { + code: 'banner-adquery-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'adquery', + params: { + placementId: '6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897', + type: 'banner300x250' + } + } + ] + } + ]; +``` diff --git a/modules/adqueryIdSystem.js b/modules/adqueryIdSystem.js new file mode 100644 index 00000000000..5357c1a1ffd --- /dev/null +++ b/modules/adqueryIdSystem.js @@ -0,0 +1,103 @@ +/** + * This module adds Adquery QID to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/adqueryIdSystem + * @requires module:modules/userId + */ + +import {ajax} from '../src/ajax.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {submodule} from '../src/hook.js'; +import * as utils from '../src/utils.js'; + +const MODULE_NAME = 'qid'; +const AU_GVLID = 902; + +export const storage = getStorageManager(AU_GVLID, 'qid'); + +/** + * Param or default. + * @param {String} param + * @param {String} defaultVal + */ +function paramOrDefault(param, defaultVal, arg) { + if (utils.isFn(param)) { + return param(arg); + } else if (utils.isStr(param)) { + return param; + } + return defaultVal; +} + +/** @type {Submodule} */ +export const adqueryIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * IAB TCF Vendor ID + * @type {string} + */ + gvlid: AU_GVLID, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param {{value:string}} value + * @returns {{qid:Object}} + */ + decode(value) { + let qid = storage.getDataFromLocalStorage('qid'); + if (utils.isStr(qid)) { + return {qid: qid}; + } + return (value && typeof value['qid'] === 'string') ? { 'qid': value['qid'] } : undefined; + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @returns {IdResponse|undefined} + */ + getId(config) { + if (!utils.isPlainObject(config.params)) { + config.params = {}; + } + const url = paramOrDefault(config.params.url, + `https://bidder.adquery.io/prebid/qid`, + config.params.urlArg); + + const resp = function (callback) { + let qid = storage.getDataFromLocalStorage('qid'); + if (utils.isStr(qid)) { + const responseObj = {qid: qid}; + callback(responseObj); + } else { + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + utils.logError(error); + } + } + callback(responseObj); + }, + error: error => { + utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + ajax(url, callbacks, undefined, {method: 'GET'}); + } + }; + return {callback: resp}; + } +}; + +submodule('userId', adqueryIdSubmodule); diff --git a/modules/adqueryIdSystem.md b/modules/adqueryIdSystem.md new file mode 100644 index 00000000000..3a49ffbe4da --- /dev/null +++ b/modules/adqueryIdSystem.md @@ -0,0 +1,35 @@ +# Adquery QID + +Adquery QID Module. For assistance setting up your module please contact us at [prebid@adquery.io](prebid@adquery.io). + +### Prebid Params + +Individual params may be set for the Adquery ID Submodule. At least one identifier must be set in the params. + +``` +pbjs.setConfig({ + usersync: { + userIds: [{ + name: 'qid', + storage: { + name: 'qid', + type: 'html5' + } + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the Adquery User ID Module integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the Adquery ID module - `"qid"` | `"qid"` | +| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. | | +| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | +| storage.name | Required | String | The name of the html5 local storage where the user ID will be stored. | `"qid"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` | +| value | Optional | Object | Used only if the page has a separate mechanism for storing the Adquery ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"qid": "2abf9f001fcd81241b67"}` | +| params | Optional | Object | Used to store params for the id system | +| params.url | Optional | String | Set an alternate GET url for qid with this parameter | +| params.urlArg | Optional | Object | Optional url parameter for params.url | diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index c6298cffde9..649031d1e3b 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -1,5 +1,8 @@ import { Renderer } from '../src/Renderer.js'; -import * as utils from '../src/utils.js'; +import { + logError, convertTypes, convertCamelToUnderscore, isArray, deepClone, logWarn, logMessage, getBidRequest, deepAccess, + isStr, createTrackPixelHtml, isEmpty, transformBidderParamKeywords, chunk, isArrayOfNums +} 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'; @@ -124,7 +127,7 @@ export const spec = { if (fpdcfg && fpdcfg.context) { let fdata = { keywords: fpdcfg.context.keywords || '', - category: utils.deepAccess(fpdcfg, 'context.data.category') || '' + category: fpdcfg.context.category || '' } payload.fpd = fdata; } @@ -145,7 +148,7 @@ export const spec = { if (!serverResponse || serverResponse.error) { let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } - utils.logError(errorMessage); + logError(errorMessage); return bids; } @@ -166,9 +169,9 @@ export const spec = { }, transformBidParams: function(params, isOpenRtb) { - params = utils.convertTypes({ + params = convertTypes({ 'placementId': 'number', - 'keywords': utils.transformBidderParamKeywords + 'keywords': transformBidderParamKeywords }, params); if (isOpenRtb) { @@ -180,7 +183,7 @@ export const spec = { } Object.keys(params).forEach(paramKey => { - let convertedKey = utils.convertCamelToUnderscore(paramKey); + let convertedKey = convertCamelToUnderscore(paramKey); if (convertedKey !== paramKey) { params[convertedKey] = params[paramKey]; delete params[paramKey]; @@ -193,7 +196,7 @@ export const spec = { } function isPopulatedArray(arr) { - return !!(utils.isArray(arr) && arr.length > 0); + return !!(isArray(arr) && arr.length > 0); } function deleteValues(keyPairObj) { @@ -206,9 +209,9 @@ function formatRequest(payload, bidderRequest) { let request = []; if (payload.tags.length > MAX_IMPS_PER_REQUEST) { - const clonedPayload = utils.deepClone(payload); + const clonedPayload = deepClone(payload); - utils.chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { + chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { clonedPayload.tags = tags; const payloadString = JSON.stringify(clonedPayload); request.push({ @@ -243,14 +246,14 @@ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { try { renderer.setRender(outstreamRender); } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); + logWarn('Prebid Error calling setRender on renderer', err); } renderer.setEventHandlers({ - impression: () => utils.logMessage('AdRelevantis outstream video impression event'), - loaded: () => utils.logMessage('AdRelevantis outstream video loaded event'), + impression: () => logMessage('AdRelevantis outstream video impression event'), + loaded: () => logMessage('AdRelevantis outstream video loaded event'), ended: () => { - utils.logMessage('AdRelevantis outstream renderer video event'); + logMessage('AdRelevantis outstream renderer video event'); document.querySelector(`#${adUnitCode}`).style.display = 'none'; } }); @@ -295,7 +298,7 @@ function handleOutstreamRendererEvents(bid, id, eventName) { * @return Bid */ function newBid(serverBid, rtbBid, bidderRequest) { - const bidRequest = utils.getBidRequest(serverBid.uuid, [bidderRequest]); + const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); const bid = { requestId: serverBid.uuid, cpm: rtbBid.cpm, @@ -324,7 +327,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { ttl: 3600 }); - const videoContext = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); switch (videoContext) { case OUTSTREAM: bid.adResponse = serverBid; @@ -334,7 +337,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { if (rtbBid.renderer_url) { const videoBid = find(bidderRequest.bids, bid => bid.bidId === serverBid.uuid); - const rendererOptions = utils.deepAccess(videoBid, 'renderer.options'); + const rendererOptions = deepAccess(videoBid, 'renderer.options'); bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions); } break; @@ -354,7 +357,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { if (jsTrackers == undefined) { jsTrackers = jsTrackerDisarmed; - } else if (utils.isStr(jsTrackers)) { + } else if (isStr(jsTrackers)) { jsTrackers = [jsTrackers, jsTrackerDisarmed]; } else { jsTrackers.push(jsTrackerDisarmed); @@ -402,10 +405,10 @@ function newBid(serverBid, rtbBid, bidderRequest) { }); try { const url = rtbBid.rtb.trackers[0].impression_urls[0]; - const tracker = utils.createTrackPixelHtml(url); + const tracker = createTrackPixelHtml(url); bid.ad += tracker; } catch (error) { - utils.logError('Error appending tracking pixel', error); + logError('Error appending tracking pixel', error); } } @@ -428,9 +431,6 @@ function bidToTag(bid) { tag.use_pmt_rule = bid.params.usePaymentRule || false tag.prebid = true; tag.disable_psa = true; - if (bid.params.reserve) { - tag.reserve = bid.params.reserve; - } if (bid.params.position) { tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; } @@ -452,8 +452,8 @@ function bidToTag(bid) { if (bid.params.externalImpId) { tag.external_imp_id = bid.params.externalImpId; } - if (!utils.isEmpty(bid.params.keywords)) { - let keywords = utils.transformBidderParamKeywords(bid.params.keywords); + if (!isEmpty(bid.params.keywords)) { + let keywords = transformBidderParamKeywords(bid.params.keywords); if (keywords.length > 0) { keywords.forEach(deleteValues); @@ -464,7 +464,7 @@ function bidToTag(bid) { tag.category = bid.params.category; } - if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) { + if (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`)) { tag.ad_types.push(NATIVE); if (tag.sizes.length === 0) { tag.sizes = transformSizes([1, 1]); @@ -476,8 +476,8 @@ function bidToTag(bid) { } } - const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = deepAccess(bid, 'mediaTypes.video.context'); tag.hb_source = 1; if (bid.mediaType === VIDEO || videoMediaType) { @@ -502,7 +502,7 @@ function bidToTag(bid) { } if ( - (utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) || + (isEmpty(bid.mediaType) && isEmpty(bid.mediaTypes)) || (bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER])) ) { tag.ad_types.push(BANNER); @@ -516,8 +516,8 @@ function transformSizes(requestSizes) { let sizes = []; let sizeObj = {}; - if (utils.isArray(requestSizes) && requestSizes.length === 2 && - !utils.isArray(requestSizes[0])) { + if (isArray(requestSizes) && requestSizes.length === 2 && + !isArray(requestSizes[0])) { sizeObj.width = parseInt(requestSizes[0], 10); sizeObj.height = parseInt(requestSizes[1], 10); sizes.push(sizeObj); @@ -578,7 +578,7 @@ function buildNativeRequest(params) { const isImageAsset = !!(requestKey === NATIVE_MAPPING.image.serverName || requestKey === NATIVE_MAPPING.icon.serverName); if (isImageAsset && request[requestKey].sizes) { let sizes = request[requestKey].sizes; - if (utils.isArrayOfNums(sizes) || (utils.isArray(sizes) && sizes.length > 0 && sizes.every(sz => utils.isArrayOfNums(sz)))) { + if (isArrayOfNums(sizes) || (isArray(sizes) && sizes.length > 0 && sizes.every(sz => isArrayOfNums(sz)))) { request[requestKey].sizes = transformSizes(request[requestKey].sizes); } } diff --git a/modules/adriverBidAdapter.js b/modules/adriverBidAdapter.js index af0a401b355..67e039e4692 100644 --- a/modules/adriverBidAdapter.js +++ b/modules/adriverBidAdapter.js @@ -1,5 +1,5 @@ // ADRIVER BID ADAPTER for Prebid 1.13 -import * as utils from '../src/utils.js'; +import { logInfo, getWindowLocation, getBidIdParameter, _each } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'adriver'; @@ -20,18 +20,24 @@ export const spec = { return !!bid.params.siteid; }, - buildRequests: function (validBidRequests) { - utils.logInfo('validBidRequests', validBidRequests); + buildRequests: function (validBidRequests, bidderRequest) { + logInfo('validBidRequests', validBidRequests); - let win = utils.getWindowLocation(); + let win = getWindowLocation(); let customID = Math.round(Math.random() * 999999999) + '-' + Math.round(new Date() / 1000) + '-1-46-'; - let siteId = utils.getBidIdParameter('siteid', validBidRequests[0].params) + ''; - let currency = utils.getBidIdParameter('currency', validBidRequests[0].params); + let siteId = getBidIdParameter('siteid', validBidRequests[0].params) + ''; + let currency = getBidIdParameter('currency', validBidRequests[0].params); currency = 'RUB'; + let timeout = null; + if (bidderRequest) { + timeout = bidderRequest.timeout + } + const payload = { 'at': 1, 'cur': [currency], + 'tmax': timeout, 'site': { 'name': win.origin, 'domain': win.hostname, @@ -40,7 +46,10 @@ export const spec = { }, 'id': customID, 'user': { - 'buyerid': 0 + 'buyerid': 0, + 'ext': { + 'eids': getUserIdAsEids(validBidRequests) + } }, 'device': { 'ip': '195.209.111.14', @@ -49,8 +58,8 @@ export const spec = { 'imp': [] }; - utils._each(validBidRequests, (bid) => { - utils._each(bid.sizes, (sizes) => { + _each(validBidRequests, (bid) => { + _each(bid.sizes, (sizes) => { let width; let height; let par; @@ -58,7 +67,7 @@ export const spec = { let floorAndCurrency = _getFloor(bid, currency, sizes); let bidFloor = floorAndCurrency.floor; - let dealId = utils.getBidIdParameter('dealid', bid.params); + let dealId = getBidIdParameter('dealid', bid.params); if (typeof sizes[0] === 'number' && typeof sizes[1] === 'number') { width = sizes[0]; height = sizes[1]; @@ -84,7 +93,7 @@ export const spec = { }] }; } - utils.logInfo('par', par); + logInfo('par', par); payload.imp.push(par); }); }); @@ -99,11 +108,11 @@ export const spec = { }, interpretResponse: function (serverResponse, bidRequest) { - utils.logInfo('serverResponse.body.seatbid', serverResponse.body.seatbid); + logInfo('serverResponse.body.seatbid', serverResponse.body.seatbid); const bidResponses = []; let nurl = 0; - utils._each(serverResponse.body.seatbid, (seatbid) => { - utils.logInfo('_each', seatbid); + _each(serverResponse.body.seatbid, (seatbid) => { + logInfo('_each', seatbid); var bid = seatbid.bid[0]; if (bid.nurl !== undefined) { nurl = bid.nurl.split('://'); @@ -126,16 +135,29 @@ export const spec = { }, ad: '' }; - utils.logInfo('bidResponse', bidResponse); + logInfo('bidResponse', bidResponse); bidResponses.push(bidResponse); } }); return bidResponses; } - }; registerBidder(spec); +/** + * get first userId from validBidRequests + * @param validBidRequests + * @returns {Array|*} userIdAsEids + */ +function getUserIdAsEids(validBidRequests) { + if (validBidRequests && validBidRequests.length > 0 && validBidRequests[0].userIdAsEids && + validBidRequests[0].userIdAsEids.length > 0) { + return validBidRequests[0].userIdAsEids; + } else { + return []; + } +} + /** * Gets bidfloor * @param {Object} bid diff --git a/modules/adspendBidAdapter.js b/modules/adspendBidAdapter.js deleted file mode 100644 index 9fe70885eeb..00000000000 --- a/modules/adspendBidAdapter.js +++ /dev/null @@ -1,164 +0,0 @@ -import * as utils from '../src/utils.js'; -import { ajax } from '../src/ajax.js' -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const storage = getStorageManager(); -const BIDDER_CODE = 'adspend'; -const BID_URL = 'https://rtb.com.ru/headerbidding-bid'; -const SYNC_URL = 'https://rtb.com.ru/headerbidding-sync?uid={UUID}'; -const COOKIE_NAME = 'hb-adspend-id'; -const TTL = 10000; -const RUB = 'RUB'; -const FIRST_PRICE = 1; -const NET_REVENUE = true; - -const winEventURLs = {}; -const placementToBidMap = {}; - -export const spec = { - code: BIDDER_CODE, - aliases: ['as'], - supportedMediaTypes: [BANNER], - - onBidWon: function(winObj) { - const requestId = winObj.requestId; - const cpm = winObj.cpm; - const event = winEventURLs[requestId].replace( - /\$\{AUCTION_PRICE\}/, - cpm - ); - - ajax(event, null); - }, - - isBidRequestValid: function(bid) { - const adServerCur = config.getConfig('currency.adServerCurrency') === RUB; - - return !!(adServerCur && - bid.params && - bid.params.bidfloor && - bid.crumbs.pubcid && - utils.checkCookieSupport() && - storage.cookiesAreEnabled() - ); - }, - - buildRequests: function(bidRequests, bidderRequest) { - const req = bidRequests[Math.floor(Math.random() * bidRequests.length)]; - const bidId = req.bidId; - const at = FIRST_PRICE; - const site = { id: req.crumbs.pubcid, domain: document.domain }; - const device = { ua: navigator.userAgent, ip: '' }; - const user = { id: getUserID() } - const cur = [ RUB ]; - const tmax = bidderRequest.timeout; - - const imp = bidRequests.map(req => { - const params = req.params; - - const tagId = params.tagId; - const id = params.placement; - const banner = { 'format': getFormats(req.sizes) }; - const bidfloor = params.bidfloor !== undefined - ? Number(params.bidfloor) : 1; - const bidfloorcur = RUB; - - placementToBidMap[id] = bidId; - - return { - id, - tagId, - banner, - bidfloor, - bidfloorcur, - secure: 0, - }; - }); - - const payload = { - bidId, - at, - site, - device, - user, - imp, - cur, - tmax, - }; - - return { - method: 'POST', - url: BID_URL, - data: JSON.stringify(payload), - }; - }, - - interpretResponse: function(resp, {bidderRequest}) { - if (resp.body === '') return []; - - const bids = resp.body.seatbid[0].bid.map(bid => { - const cpm = bid.price; - const impid = bid.impid; - const requestId = placementToBidMap[impid]; - const width = bid.w; - const height = bid.h; - const creativeId = bid.adid; - const dealId = bid.dealid; - const currency = resp.body.cur; - const netRevenue = NET_REVENUE; - const ttl = TTL; - const ad = bid.adm; - - return { - cpm, - requestId, - width, - height, - creativeId, - dealId, - currency, - netRevenue, - ttl, - ad, - }; - }); - - return bids; - }, - - getUserSyncs: function(syncOptions, resps) { - let syncs = []; - - resps.forEach(resp => { - if (syncOptions.pixelEnabled && resp.body === '') { - const uuid = getUserID(); - syncs.push({ - type: 'image', - url: SYNC_URL.replace('{UUID}', uuid), - }); - } - }); - - return syncs - } -} - -const getUserID = () => { - const i = storage.getCookie(COOKIE_NAME); - - if (i === null) { - const uuid = utils.generateUUID(); - storage.setCookie(COOKIE_NAME, uuid); - return uuid; - } - return i; -}; - -const getFormats = arr => arr.map((s) => { - return { w: s[0], h: s[1] }; -}); - -registerBidder(spec); diff --git a/modules/adtargetBidAdapter.js b/modules/adtargetBidAdapter.js index b22addec54f..0ad0177815a 100644 --- a/modules/adtargetBidAdapter.js +++ b/modules/adtargetBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +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'; @@ -13,7 +13,7 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { - return !!utils.deepAccess(bid, 'params.aid'); + return !!deepAccess(bid, 'params.aid'); }, getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; @@ -42,9 +42,9 @@ export const spec = { } if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { - utils.isArray(serverResponses) && serverResponses.forEach((response) => { + isArray(serverResponses) && serverResponses.forEach((response) => { if (response.body) { - if (utils.isArray(response.body)) { + if (isArray(response.body)) { response.body.forEach(b => { addSyncs(b); }) @@ -59,10 +59,10 @@ export const spec = { buildRequests: function (bidRequests, adapterRequest) { const adapterSettings = config.getConfig(adapterRequest.bidderCode) - const chunkSize = utils.deepAccess(adapterSettings, 'chunkSize', 10); + const chunkSize = deepAccess(adapterSettings, 'chunkSize', 10); const { tag, bids } = bidToTag(bidRequests, adapterRequest); - const bidChunks = utils.chunk(bids, chunkSize); - return utils._map(bidChunks, (bids) => { + const bidChunks = chunk(bids, chunkSize); + return _map(bidChunks, (bids) => { return { data: Object.assign({}, tag, { BidRequests: bids }), adapterRequest, @@ -75,12 +75,12 @@ export const spec = { serverResponse = serverResponse.body; let bids = []; - if (!utils.isArray(serverResponse)) { + if (!isArray(serverResponse)) { return parseResponse(serverResponse, adapterRequest); } serverResponse.forEach(serverBidResponse => { - bids = utils.flatten(bids, parseResponse(serverBidResponse, adapterRequest)); + bids = flatten(bids, parseResponse(serverBidResponse, adapterRequest)); }); return bids; @@ -88,14 +88,14 @@ export const spec = { }; function parseResponse(serverResponse, adapterRequest) { - const isInvalidValidResp = !serverResponse || !utils.isArray(serverResponse.bids); + const isInvalidValidResp = !serverResponse || !isArray(serverResponse.bids); const bids = []; if (isInvalidValidResp) { const extMessage = serverResponse && serverResponse.ext && serverResponse.ext.message ? `: ${serverResponse.ext.message}` : ''; const errorMessage = `in response for ${adapterRequest.bidderCode} adapter ${extMessage}`; - utils.logError(errorMessage); + logError(errorMessage); return bids; } @@ -117,23 +117,23 @@ function parseResponse(serverResponse, adapterRequest) { function bidToTag(bidRequests, adapterRequest) { const tag = { - Domain: utils.deepAccess(adapterRequest, 'refererInfo.referer') + Domain: deepAccess(adapterRequest, 'refererInfo.referer') }; if (config.getConfig('coppa') === true) { tag.Coppa = 1; } - if (utils.deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) { + if (deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) { tag.GDPR = 1; - tag.GDPRConsent = utils.deepAccess(adapterRequest, 'gdprConsent.consentString'); + tag.GDPRConsent = deepAccess(adapterRequest, 'gdprConsent.consentString'); } - if (utils.deepAccess(adapterRequest, 'uspConsent')) { - tag.USP = utils.deepAccess(adapterRequest, 'uspConsent'); + if (deepAccess(adapterRequest, 'uspConsent')) { + tag.USP = deepAccess(adapterRequest, 'uspConsent'); } - if (utils.deepAccess(bidRequests[0], 'schain')) { - tag.Schain = utils.deepAccess(bidRequests[0], 'schain'); + if (deepAccess(bidRequests[0], 'schain')) { + tag.Schain = deepAccess(bidRequests[0], 'schain'); } - if (utils.deepAccess(bidRequests[0], 'userId')) { - tag.UserIds = utils.deepAccess(bidRequests[0], 'userId'); + if (deepAccess(bidRequests[0], 'userId')) { + tag.UserIds = deepAccess(bidRequests[0], 'userId'); } const bids = [] @@ -147,19 +147,19 @@ function bidToTag(bidRequests, adapterRequest) { } function prepareBidRequests(bidReq) { - const mediaType = utils.deepAccess(bidReq, 'mediaTypes.video') ? VIDEO : DISPLAY; - const sizes = mediaType === VIDEO ? utils.deepAccess(bidReq, 'mediaTypes.video.playerSize') : utils.deepAccess(bidReq, 'mediaTypes.banner.sizes'); + const mediaType = deepAccess(bidReq, 'mediaTypes.video') ? VIDEO : DISPLAY; + const sizes = mediaType === VIDEO ? deepAccess(bidReq, 'mediaTypes.video.playerSize') : deepAccess(bidReq, 'mediaTypes.banner.sizes'); const bidReqParams = { 'CallbackId': bidReq.bidId, 'Aid': bidReq.params.aid, 'AdType': mediaType, - 'Sizes': utils.parseSizesInput(sizes).join(',') + 'Sizes': parseSizesInput(sizes).join(',') }; return bidReqParams; } function getMediaType(bidderRequest) { - return utils.deepAccess(bidderRequest, 'mediaTypes.video') ? VIDEO : BANNER; + return deepAccess(bidderRequest, 'mediaTypes.video') ? VIDEO : BANNER; } function createBid(bidResponse, bidRequest) { @@ -173,7 +173,10 @@ function createBid(bidResponse, bidRequest) { cpm: bidResponse.cpm, netRevenue: true, mediaType, - ttl: 300 + ttl: 300, + meta: { + advertiserDomains: bidResponse.adomain || [] + } }; if (mediaType === BANNER) { diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index 9ca4b95dfd2..44a9c90d438 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +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'; @@ -20,6 +20,7 @@ const HOST_GETTERS = { onefiftytwomedia: () => 'ghb.ads.152media.com', mediafuse: () => 'ghb.hbmp.mediafuse.com', bidsxchange: () => 'ghb.hbd.bidsxchange.com', + streamkey: () => 'ghb.hb.streamkey.net', } const getUri = function (bidderCode) { let bidderWithoutSuffix = bidderCode.split('_')[0]; @@ -35,7 +36,7 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, gvlid: 410, - aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange', + aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange', 'streamkey', { code: 'navelix', gvlid: 380 }, { code: 'mediafuse', @@ -44,7 +45,7 @@ export const spec = { ], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { - return !!utils.deepAccess(bid, 'params.aid'); + return !!deepAccess(bid, 'params.aid'); }, getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; @@ -73,9 +74,9 @@ export const spec = { } if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { - utils.isArray(serverResponses) && serverResponses.forEach((response) => { + isArray(serverResponses) && serverResponses.forEach((response) => { if (response.body) { - if (utils.isArray(response.body)) { + if (isArray(response.body)) { response.body.forEach(b => { addSyncs(b); }) @@ -94,10 +95,10 @@ export const spec = { */ buildRequests: function (bidRequests, adapterRequest) { const adapterSettings = config.getConfig(adapterRequest.bidderCode) - const chunkSize = utils.deepAccess(adapterSettings, 'chunkSize', 10); + const chunkSize = deepAccess(adapterSettings, 'chunkSize', 10); const { tag, bids } = bidToTag(bidRequests, adapterRequest); - const bidChunks = utils.chunk(bids, chunkSize); - return utils._map(bidChunks, (bids) => { + const bidChunks = chunk(bids, chunkSize); + return _map(bidChunks, (bids) => { return { data: Object.assign({}, tag, { BidRequests: bids }), adapterRequest, @@ -117,26 +118,26 @@ export const spec = { serverResponse = serverResponse.body; let bids = []; - if (!utils.isArray(serverResponse)) { + if (!isArray(serverResponse)) { return parseRTBResponse(serverResponse, adapterRequest); } serverResponse.forEach(serverBidResponse => { - bids = utils.flatten(bids, parseRTBResponse(serverBidResponse, adapterRequest)); + bids = flatten(bids, parseRTBResponse(serverBidResponse, adapterRequest)); }); return bids; }, transformBidParams(params) { - return utils.convertTypes({ + return convertTypes({ 'aid': 'number', }, params); } }; function parseRTBResponse(serverResponse, adapterRequest) { - const isEmptyResponse = !serverResponse || !utils.isArray(serverResponse.bids); + const isEmptyResponse = !serverResponse || !isArray(serverResponse.bids); const bids = []; if (isEmptyResponse) { @@ -161,24 +162,31 @@ function parseRTBResponse(serverResponse, adapterRequest) { function bidToTag(bidRequests, adapterRequest) { // start publisher env const tag = { - Domain: utils.deepAccess(adapterRequest, 'refererInfo.referer') + Domain: deepAccess(adapterRequest, 'refererInfo.referer') }; if (config.getConfig('coppa') === true) { tag.Coppa = 1; } - if (utils.deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) { + if (deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) { tag.GDPR = 1; - tag.GDPRConsent = utils.deepAccess(adapterRequest, 'gdprConsent.consentString'); + tag.GDPRConsent = deepAccess(adapterRequest, 'gdprConsent.consentString'); } - if (utils.deepAccess(adapterRequest, 'uspConsent')) { - tag.USP = utils.deepAccess(adapterRequest, 'uspConsent'); + if (deepAccess(adapterRequest, 'uspConsent')) { + tag.USP = deepAccess(adapterRequest, 'uspConsent'); } - if (utils.deepAccess(bidRequests[0], 'schain')) { - tag.Schain = utils.deepAccess(bidRequests[0], 'schain'); + if (deepAccess(bidRequests[0], 'schain')) { + tag.Schain = deepAccess(bidRequests[0], 'schain'); } - if (utils.deepAccess(bidRequests[0], 'userId')) { - tag.UserIds = utils.deepAccess(bidRequests[0], 'userId'); + if (deepAccess(bidRequests[0], 'userId')) { + tag.UserIds = deepAccess(bidRequests[0], 'userId'); } + if (deepAccess(bidRequests[0], 'userIdAsEids')) { + tag.UserEids = deepAccess(bidRequests[0], 'userIdAsEids'); + } + if (window.adtDmp && window.adtDmp.ready) { + tag.DMPId = window.adtDmp.getUID(); + } + // end publisher env const bids = [] @@ -196,13 +204,13 @@ function bidToTag(bidRequests, adapterRequest) { * @returns {object} */ function prepareBidRequests(bidReq) { - const mediaType = utils.deepAccess(bidReq, 'mediaTypes.video') ? VIDEO : DISPLAY; - const sizes = mediaType === VIDEO ? utils.deepAccess(bidReq, 'mediaTypes.video.playerSize') : utils.deepAccess(bidReq, 'mediaTypes.banner.sizes'); + const mediaType = deepAccess(bidReq, 'mediaTypes.video') ? VIDEO : DISPLAY; + const sizes = mediaType === VIDEO ? deepAccess(bidReq, 'mediaTypes.video.playerSize') : deepAccess(bidReq, 'mediaTypes.banner.sizes'); const bidReqParams = { 'CallbackId': bidReq.bidId, 'Aid': bidReq.params.aid, 'AdType': mediaType, - 'Sizes': utils.parseSizesInput(sizes).join(',') + 'Sizes': parseSizesInput(sizes).join(',') }; bidReqParams.PlacementId = bidReq.adUnitCode; @@ -213,9 +221,9 @@ function prepareBidRequests(bidReq) { bidReqParams.PlacementId = bidReq.params.vpb_placement_id; } if (mediaType === VIDEO) { - const context = utils.deepAccess(bidReq, 'mediaTypes.video.context'); + const context = deepAccess(bidReq, 'mediaTypes.video.context'); if (context === ADPOD) { - bidReqParams.Adpod = utils.deepAccess(bidReq, 'mediaTypes.video'); + bidReqParams.Adpod = deepAccess(bidReq, 'mediaTypes.video'); } } return bidReqParams; @@ -227,7 +235,7 @@ function prepareBidRequests(bidReq) { * @returns {object} */ function getMediaType(bidderRequest) { - return utils.deepAccess(bidderRequest, 'mediaTypes.video') ? VIDEO : BANNER; + return deepAccess(bidderRequest, 'mediaTypes.video') ? VIDEO : BANNER; } /** @@ -238,7 +246,7 @@ function getMediaType(bidderRequest) { */ function createBid(bidResponse, bidRequest) { const mediaType = getMediaType(bidRequest) - const context = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); + const context = deepAccess(bidRequest, 'mediaTypes.video.context'); const bid = { requestId: bidResponse.requestId, creativeId: bidResponse.cmpId, @@ -248,7 +256,10 @@ function createBid(bidResponse, bidRequest) { cpm: bidResponse.cpm, netRevenue: true, mediaType, - ttl: 300 + ttl: 300, + meta: { + advertiserDomains: bidResponse.adomain || [] + } }; if (mediaType === BANNER) { diff --git a/modules/adtelligentIdSystem.js b/modules/adtelligentIdSystem.js new file mode 100644 index 00000000000..fb3b5f6fe2a --- /dev/null +++ b/modules/adtelligentIdSystem.js @@ -0,0 +1,91 @@ +/** + * This module adds Adtelligent DMP Tokens to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/adtelligentIdSystem + * @requires module:modules/userId + */ + +import * as ajax from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; + +const gvlid = 410; +const moduleName = 'adtelligent'; +const syncUrl = 'https://idrs.adtelligent.com/get'; + +function buildUrl(opts) { + const queryPairs = []; + for (let key in opts) { + queryPairs.push(`${key}=${encodeURIComponent(opts[key])}`); + } + return `${syncUrl}?${queryPairs.join('&')}`; +} + +function requestRemoteIdAsync(url, cb) { + ajax.ajaxBuilder()( + url, + { + success: response => { + const jsonResponse = JSON.parse(response); + const { u: dmpId } = jsonResponse; + cb(dmpId); + }, + error: () => { + cb(); + } + }, + null, + { + method: 'GET', + contentType: 'application/json', + withCredentials: true + } + ); +} + +/** @type {Submodule} */ +export const adtelligentIdModule = { + /** + * used to link submodule with config + * @type {string} + */ + name: moduleName, + gvlid: gvlid, + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{adtelligentId: string}} + */ + decode(uid) { + return { adtelligentId: uid }; + }, + /** + * get the Adtelligent Id from local storages and initiate a new user sync + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData} [consentData] + * @returns {IdResponse} + */ + getId(config, consentData) { + const gdpr = consentData && consentData.gdprApplies ? 1 : 0; + const gdprConsent = gdpr ? consentData.consentString : ''; + const url = buildUrl({ + gdpr, + gdprConsent + }); + + if (window.adtDmp && window.adtDmp.ready) { + return { id: window.adtDmp.getUID() } + } + + return { + callback: (cb) => { + requestRemoteIdAsync(url, (id) => { + cb(id); + }); + } + + } + } +}; + +submodule('userId', adtelligentIdModule); diff --git a/modules/adtelligentIdSystem.md b/modules/adtelligentIdSystem.md new file mode 100644 index 00000000000..291a5e70bde --- /dev/null +++ b/modules/adtelligentIdSystem.md @@ -0,0 +1,33 @@ +### Adtelligent Id Sytem + +The [Adtelligent](https://adtelligent.com) ID system is a uniq per-session user identifier for providing high quality DMP data for advertisers + +#### Adtelligent Id Sytem Configuration Example + +{% highlight javascript %} + pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'adtelligent' + }] + } + }); +{% endhighlight %} + +Example with a short storage for ~10 minutes and refresh in 5 minutes: + +{% highlight javascript %} + pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'adtelligent', + storage: { + type: "html5", + name: "adt_id", + expires:0.003, + refreshInSeconds: 60 * 5 + } + }] + } + }); +{% endhighlight %} diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index b4dc7f7ea89..df848fba823 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { logWarn, isArray, inIframe, isNumber, isStr, deepClone, deepSetValue, logError, deepAccess, isBoolean } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -77,15 +77,6 @@ function _getDomainFromURL(url) { return anchor.hostname; } -function _parseSlotParam(paramName, paramValue) { - switch (paramName) { - case 'reserve': - return parseFloat(paramValue) || UNDEFINED; - default: - return paramValue; - } -} - let platform = (function getPlatform() { var ua = navigator.userAgent; if (ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1) { @@ -199,22 +190,22 @@ function _checkParamDataType(key, value, datatype) { var functionToExecute; switch (datatype) { case DATA_TYPES.BOOLEAN: - functionToExecute = utils.isBoolean; + functionToExecute = isBoolean; break; case DATA_TYPES.NUMBER: - functionToExecute = utils.isNumber; + functionToExecute = isNumber; break; case DATA_TYPES.STRING: - functionToExecute = utils.isStr; + functionToExecute = isStr; break; case DATA_TYPES.ARRAY: - functionToExecute = utils.isArray; + functionToExecute = isArray; break; } if (functionToExecute(value)) { return value; } - utils.logWarn(LOG_WARN_PREFIX + errMsg); + logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } @@ -225,7 +216,7 @@ function _parseNativeResponse(bid, newBid) { try { adm = JSON.parse(bid.adm.replace(/\\/g, '')); } catch (ex) { - // utils.logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); + // logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); return; } if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { @@ -283,13 +274,13 @@ function _createBannerRequest(bid) { var sizes = bid.mediaTypes.banner.sizes; var format = []; var bannerObj; - if (sizes !== UNDEFINED && utils.isArray(sizes)) { + if (sizes !== UNDEFINED && isArray(sizes)) { bannerObj = {}; if (!bid.params.width && !bid.params.height) { if (sizes.length === 0) { // i.e. since bid.params does not have width or height, and length of sizes is 0, need to ignore this banner imp bannerObj = UNDEFINED; - utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); + logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); return bannerObj; } else { bannerObj.w = parseInt(sizes[0][0], 10); @@ -312,9 +303,9 @@ function _createBannerRequest(bid) { } } bannerObj.pos = 0; - bannerObj.topframe = utils.inIframe() ? 0 : 1; + bannerObj.topframe = inIframe() ? 0 : 1; } else { - utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); + logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); bannerObj = UNDEFINED; } return bannerObj; @@ -332,10 +323,10 @@ function _createVideoRequest(bid) { } } // read playersize and assign to h and w. - if (utils.isArray(bid.mediaTypes.video.playerSize[0])) { + if (isArray(bid.mediaTypes.video.playerSize[0])) { videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0][0], 10); videoObj.h = parseInt(bid.mediaTypes.video.playerSize[0][1], 10); - } else if (utils.isNumber(bid.mediaTypes.video.playerSize[0])) { + } else if (isNumber(bid.mediaTypes.video.playerSize[0])) { videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0], 10); videoObj.h = parseInt(bid.mediaTypes.video.playerSize[1], 10); } @@ -346,7 +337,7 @@ function _createVideoRequest(bid) { } } else { videoObj = UNDEFINED; - utils.logWarn(LOG_WARN_PREFIX + 'Error: Video config params missing for adunit: ' + bid.params.adUnit + ' with mediaType set as video. Ignoring video impression in the adunit.'); + logWarn(LOG_WARN_PREFIX + 'Error: Video config params missing for adunit: ' + bid.params.adUnit + ' with mediaType set as video. Ignoring video impression in the adunit.'); } return videoObj; } @@ -364,7 +355,7 @@ function _checkMediaType(adm, newBid) { newBid.mediaType = NATIVE; } } catch (e) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + adm); + logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + adm); } } } @@ -380,7 +371,7 @@ function _createImpressionObject(bid, conf) { impObj = { id: bid.bidId, tagid: String(bid.params.zoneId || undefined), - bidfloor: _parseSlotParam('reserve', bid.params.reserve), + bidfloor: 0, secure: 1, ext: {}, bidfloorcur: ADTRUE_CURRENCY @@ -410,9 +401,9 @@ function _createImpressionObject(bid, conf) { pos: 0, w: bid.params.width, h: bid.params.height, - topframe: utils.inIframe() ? 0 : 1 + topframe: inIframe() ? 0 : 1 }; - if (utils.isArray(sizes) && sizes.length > 1) { + if (isArray(sizes) && sizes.length > 1) { sizes = sizes.splice(1, sizes.length - 1); sizes.forEach(size => { format.push({ @@ -437,19 +428,19 @@ export const spec = { isBidRequestValid: function (bid) { if (bid && bid.params) { if (!bid.params.zoneId) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: missing zoneId'); + logWarn(LOG_WARN_PREFIX + 'Error: missing zoneId'); return false; } if (!bid.params.publisherId) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: missing publisherId'); + logWarn(LOG_WARN_PREFIX + 'Error: missing publisherId'); return false; } - if (!utils.isStr(bid.params.publisherId)) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric'); + if (!isStr(bid.params.publisherId)) { + logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric'); return false; } - if (!utils.isStr(bid.params.zoneId)) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: zoneId is mandatory and cannot be numeric'); + if (!isStr(bid.params.zoneId)) { + logWarn(LOG_WARN_PREFIX + 'Error: zoneId is mandatory and cannot be numeric'); return false; } return true; @@ -467,7 +458,7 @@ export const spec = { let bidCurrency = ''; let bid; validBidRequests.forEach(originalBid => { - bid = utils.deepClone(originalBid); + bid = deepClone(originalBid); _parseAdSlot(bid); conf.zoneId = conf.zoneId || bid.params.zoneId; @@ -477,7 +468,7 @@ export const spec = { if (bidCurrency === '') { bidCurrency = bid.params.currency || UNDEFINED; } else if (bid.params.hasOwnProperty('currency') && bidCurrency !== bid.params.currency) { - utils.logWarn(LOG_WARN_PREFIX + 'Currency specifier ignored. Only one currency permitted.'); + logWarn(LOG_WARN_PREFIX + 'Currency specifier ignored. Only one currency permitted.'); } bid.params.currency = bidCurrency; @@ -511,28 +502,28 @@ export const spec = { if (typeof config.getConfig('device') === 'object') { payload.device = Object.assign(payload.device, config.getConfig('device')); } - utils.deepSetValue(payload, 'source.tid', conf.transactionId); + deepSetValue(payload, 'source.tid', conf.transactionId); // test bids if (window.location.href.indexOf('adtrueTest=true') !== -1) { payload.test = 1; } // adding schain object if (validBidRequests[0].schain) { - utils.deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); + deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); } // Attaching GDPR Consent Params if (bidderRequest && bidderRequest.gdprConsent) { - utils.deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - utils.deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } // CCPA if (bidderRequest && bidderRequest.uspConsent) { - utils.deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } // coppa compliance if (config.getConfig('coppa') === true) { - utils.deepSetValue(payload, 'regs.coppa', 1); + deepSetValue(payload, 'regs.coppa', 1); } return { @@ -548,12 +539,12 @@ export const spec = { let parsedRequest = JSON.parse(bidderRequest.data); let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : ''; try { - if (serverResponses.body && serverResponses.body.seatbid && utils.isArray(serverResponses.body.seatbid)) { + if (serverResponses.body && serverResponses.body.seatbid && isArray(serverResponses.body.seatbid)) { // Supporting multiple bid responses for same adSize respCur = serverResponses.body.cur || respCur; serverResponses.body.seatbid.forEach(seatbidder => { seatbidder.bid && - utils.isArray(seatbidder.bid) && + isArray(seatbidder.bid) && seatbidder.bid.forEach(bid => { let newBid = { requestId: bid.impid, @@ -607,7 +598,7 @@ export const spec = { }); } } catch (error) { - utils.logError(error); + logError(error); } return bidResponses; }, @@ -616,7 +607,7 @@ export const spec = { return []; } return responses.reduce((accum, rsp) => { - let cookieSyncs = utils.deepAccess(rsp, 'body.ext.cookie_sync'); + let cookieSyncs = deepAccess(rsp, 'body.ext.cookie_sync'); if (cookieSyncs) { let cookieSyncObjects = cookieSyncs.map(cookieSync => { return { diff --git a/modules/aduptechBidAdapter.js b/modules/aduptechBidAdapter.js index d5b348ee29b..1186e0410ab 100644 --- a/modules/aduptechBidAdapter.js +++ b/modules/aduptechBidAdapter.js @@ -1,20 +1,169 @@ +import { deepAccess, getWindowTop, getWindowSelf, getAdUnitSizes } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js' -import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js' export const BIDDER_CODE = 'aduptech'; -export const PUBLISHER_PLACEHOLDER = '{PUBLISHER}'; -export const ENDPOINT_URL = 'https://rtb.d.adup-tech.com/prebid/' + PUBLISHER_PLACEHOLDER + '_bid'; +export const ENDPOINT_URL_PUBLISHER_PLACEHOLDER = '{PUBLISHER}'; +export const ENDPOINT_URL = 'https://rtb.d.adup-tech.com/prebid/' + ENDPOINT_URL_PUBLISHER_PLACEHOLDER + '_bid'; export const ENDPOINT_METHOD = 'POST'; +/** + * Internal utitlity functions + */ +export const internal = { + + /** + * Extracts the GDPR information from given bidderRequest + * + * @param {BidderRequest} bidderRequest + * @returns {null|Object.} + */ + extractGdpr: (bidderRequest) => { + if (bidderRequest && bidderRequest.gdprConsent) { + return { + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + }; + } + + return null; + }, + + /** + * Extracts the pageUrl from given bidderRequest.refererInfo or gobal "pageUrl" config or from (top) window location + * + * @param {BidderRequest} bidderRequest + * @returns {string} + */ + extractPageUrl: (bidderRequest) => { + if (bidderRequest && deepAccess(bidderRequest, 'refererInfo.canonicalUrl')) { + return bidderRequest.refererInfo.canonicalUrl; + } + + if (config && config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + + try { + return getWindowTop().location.href; + } catch (e) { + return getWindowSelf().location.href; + } + }, + + /** + * Extracts the referrer based on given bidderRequest.refererInfo or from (top) document referrer + * + * @param {BidderRequest} bidderRequest + * @returns {string} + */ + extractReferrer: (bidderRequest) => { + if (bidderRequest && deepAccess(bidderRequest, 'refererInfo.referer')) { + return bidderRequest.refererInfo.referer; + } + + try { + return getWindowTop().document.referrer; + } catch (e) { + return getWindowSelf().document.referrer; + } + }, + + /** + * Extracts banner config from given bidRequest + * + * @param {BidRequest} bidRequest + * @returns {null|Object.} + */ + extractBannerConfig: (bidRequest) => { + const sizes = getAdUnitSizes(bidRequest); + if (Array.isArray(sizes) && sizes.length > 0) { + return { sizes: sizes }; + } + + return null; + }, + + /** + * Extracts native config from given bidRequest + * + * @param {BidRequest} bidRequest + * @returns {null|Object.} + */ + extractNativeConfig: (bidRequest) => { + if (bidRequest && deepAccess(bidRequest, 'mediaTypes.native')) { + return bidRequest.mediaTypes.native; + } + + return null; + }, + + /** + * Extracts the bidder params from given bidRequest + * + * @param {BidRequest} bidRequest + * @returns {null|Object.} + */ + extractParams: (bidRequest) => { + if (bidRequest && bidRequest.params) { + return bidRequest.params + } + + return null; + }, + + /** + * Group given array of bidRequests by params.publisher + * + * @param {BidRequest[]} bidRequests + * @returns {Object.} + */ + groupBidRequestsByPublisher: (bidRequests) => { + const groupedBidRequests = {}; + + if (!bidRequests || bidRequests.length === 0) { + return groupedBidRequests; + } + + bidRequests.forEach((bidRequest) => { + const publisher = internal.extractParams(bidRequest).publisher; + if (!publisher) { + return; + } + + if (!groupedBidRequests[publisher]) { + groupedBidRequests[publisher] = []; + } + + groupedBidRequests[publisher].push(bidRequest); + }); + + return groupedBidRequests; + }, + + /** + * Build ednpoint url based on given publisher code + * + * @param {string} publisher + * @returns {string} + */ + buildEndpointUrl: (publisher) => { + return ENDPOINT_URL.replace(ENDPOINT_URL_PUBLISHER_PLACEHOLDER, encodeURIComponent(publisher)); + }, +} + +/** + * The bid adapter definition + */ export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, NATIVE], /** * Validate given bid request * - * @param {*} bidRequest + * @param {BidRequest[]} bidRequest * @returns {boolean} */ isBidRequestValid: (bidRequest) => { @@ -22,12 +171,13 @@ export const spec = { return false; } - const sizes = extractSizesFromBidRequest(bidRequest); - if (!sizes || sizes.length === 0) { + // banner or native config has to be set + if (!internal.extractBannerConfig(bidRequest) && !internal.extractNativeConfig(bidRequest)) { return false; } - const params = extractParamsFromBidRequest(bidRequest); + // publisher and placement param has to be set + const params = internal.extractParams(bidRequest); if (!params || !params.publisher || !params.placement) { return false; } @@ -38,152 +188,124 @@ export const spec = { /** * Build real bid requests * - * @param {*} validBidRequests - * @param {*} bidderRequest - * @returns {*[]} + * @param {BidRequest[]} validBidRequests + * @param {BidderRequest} bidderRequest + * @returns {Object[]} */ buildRequests: (validBidRequests, bidderRequest) => { - const bidRequests = []; - const gdpr = extractGdprFromBidderRequest(bidderRequest); + const requests = []; + + // stop here on invalid or empty data + if (!bidderRequest || !validBidRequests || validBidRequests.length === 0) { + return requests; + } + + // collect required data + const auctionId = bidderRequest.auctionId; + const pageUrl = internal.extractPageUrl(bidderRequest); + const referrer = internal.extractReferrer(bidderRequest); + const gdpr = internal.extractGdpr(bidderRequest); - validBidRequests.forEach((bidRequest) => { - bidRequests.push({ - url: ENDPOINT_URL.replace(PUBLISHER_PLACEHOLDER, encodeURIComponent(bidRequest.params.publisher)), + // group bid requests by publisher + const groupedBidRequests = internal.groupBidRequestsByPublisher(validBidRequests); + + // build requests + for (const publisher in groupedBidRequests) { + const request = { + url: internal.buildEndpointUrl(publisher), method: ENDPOINT_METHOD, data: { + auctionId: auctionId, + pageUrl: pageUrl, + referrer: referrer, + imp: [] + } + }; + + // add gdpr data + if (gdpr) { + request.data.gdpr = gdpr; + } + + // handle multiple bids per request + groupedBidRequests[publisher].forEach((bidRequest) => { + const bid = { bidId: bidRequest.bidId, - auctionId: bidRequest.auctionId, transactionId: bidRequest.transactionId, adUnitCode: bidRequest.adUnitCode, - pageUrl: extractTopWindowUrlFromBidRequest(bidRequest), - referrer: extractTopWindowReferrerFromBidRequest(bidRequest), - sizes: extractSizesFromBidRequest(bidRequest), - params: extractParamsFromBidRequest(bidRequest), - gdpr: gdpr + params: internal.extractParams(bidRequest) + }; + + // add banner config + const bannerConfig = internal.extractBannerConfig(bidRequest); + if (bannerConfig) { + bid.banner = bannerConfig; + } + + // add native config + const nativeConfig = internal.extractNativeConfig(bidRequest); + if (nativeConfig) { + bid.native = nativeConfig; } + + request.data.imp.push(bid); }); - }); - return bidRequests; + requests.push(request); + } + + return requests; }, /** * Handle bid response * - * @param {*} response - * @returns {*[]} + * @param {Object} response + * @returns {Object[]} */ interpretResponse: (response) => { const bidResponses = []; - if (!response.body || !response.body.bid || !response.body.creative) { + // stop here on invalid or empty data + if (!response || !deepAccess(response, 'body.bids') || response.body.bids.length === 0) { return bidResponses; } - bidResponses.push({ - requestId: response.body.bid.bidId, - cpm: response.body.bid.price, - netRevenue: response.body.bid.net, - currency: response.body.bid.currency, - ttl: response.body.bid.ttl, - creativeId: response.body.creative.id, - width: response.body.creative.width, - height: response.body.creative.height, - ad: response.body.creative.html - }); - - return bidResponses; - } -}; - -/** - * Extracts the possible ad unit sizes from given bid request - * - * @param {*} bidRequest - * @returns {number[]} - */ -export function extractSizesFromBidRequest(bidRequest) { - // since pbjs 3.0 - if (bidRequest && utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes')) { - return bidRequest.mediaTypes.banner.sizes; - - // for backward compatibility - } else if (bidRequest && bidRequest.sizes) { - return bidRequest.sizes; - - // fallback - } else { - return []; - } -} - -/** - * Extracts the custom params from given bid request - * - * @param {*} bidRequest - * @returns {*} - */ -export function extractParamsFromBidRequest(bidRequest) { - if (bidRequest && bidRequest.params) { - return bidRequest.params - } else { - return null; - } -} - -/** - * Extracts the GDPR information from given bidder request - * - * @param {*} bidderRequest - * @returns {*} - */ -export function extractGdprFromBidderRequest(bidderRequest) { - let gdpr = null; - - if (bidderRequest && bidderRequest.gdprConsent) { - gdpr = { - consentString: bidderRequest.gdprConsent.consentString, - consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true - }; - } + // parse multiple bids per response + response.body.bids.forEach((bid) => { + if (!bid || !bid.bid || !bid.creative) { + return; + } - return gdpr; -} + const bidResponse = { + requestId: bid.bid.bidId, + cpm: bid.bid.price, + netRevenue: bid.bid.net, + currency: bid.bid.currency, + ttl: bid.bid.ttl, + creativeId: bid.creative.id, + meta: { + advertiserDomains: bid.creative.advertiserDomains + } + } -/** - * Extracts the page url from given bid request or use the (top) window location as fallback - * - * @param {*} bidRequest - * @returns {string} - */ -export function extractTopWindowUrlFromBidRequest(bidRequest) { - if (bidRequest && utils.deepAccess(bidRequest, 'refererInfo.canonicalUrl')) { - return bidRequest.refererInfo.canonicalUrl; - } + if (bid.creative.html) { + bidResponse.mediaType = BANNER; + bidResponse.ad = bid.creative.html; + bidResponse.width = bid.creative.width; + bidResponse.height = bid.creative.height; + } - try { - return window.top.location.href; - } catch (e) { - return window.location.href; - } -} + if (bid.creative.native) { + bidResponse.mediaType = NATIVE; + bidResponse.native = bid.creative.native; + } -/** - * Extracts the referrer from given bid request or use the (top) document referrer as fallback - * - * @param {*} bidRequest - * @returns {string} - */ -export function extractTopWindowReferrerFromBidRequest(bidRequest) { - if (bidRequest && utils.deepAccess(bidRequest, 'refererInfo.referer')) { - return bidRequest.refererInfo.referer; - } + bidResponses.push(bidResponse); + }); - try { - return window.top.document.referrer; - } catch (e) { - return window.document.referrer; + return bidResponses; } -} +}; registerBidder(spec); diff --git a/modules/aduptechBidAdapter.md b/modules/aduptechBidAdapter.md index 281fe24cf9d..25034cecbe8 100644 --- a/modules/aduptechBidAdapter.md +++ b/modules/aduptechBidAdapter.md @@ -1,35 +1,86 @@ -# Overview -``` -Module Name: AdUp Technology Bid Adapter -Module Type: Bidder Adapter -Maintainers: - - steffen.anders@adup-tech.com - - sebastian.briesemeister@adup-tech.com - - marten.lietz@adup-tech.com -``` +# AdUp Technology Bid Adapter -# Description +## Description Connects to AdUp Technology demand sources to fetch bids. -Please use ```aduptech``` as bidder code. Only banner formats are supported. -The AdUp Technology bidding adapter requires setup and approval before beginning. -For more information visit [www.adup-tech.com](https://www.adup-tech.com/en) or contact [info@adup-tech.com](mailto:info@adup-tech.com). +**Note:** The bid adapter requires correct setup and approval, including an existing publisher account. For more information visit [www.adup-tech.com](https://www.adup-tech.com/en) or contact [info@adup-tech.com](mailto:info@adup-tech.com). + + +## Overview +- Module Name: AdUp Technology Bid Adapter +- Module Type: Bidder Adapter +- Maintainers: + - [steffen.anders@adup-tech.com](mailto:steffen.anders@adup-tech.com) + - [sebastian.briesemeister@adup-tech.com](mailto:sebastian.briesemeister@adup-tech.com) + - [marten.lietz@adup-tech.com](mailto:marten.lietz@adup-tech.com) +- Bidder code: `aduptech` +- Supported media types: `banner`, `native` + +## Paramters +| Name | Scope | Description | Example | +| :--- | :---- | :---------- | :------ | +| `publisher` | required | Unique publisher identifier | `'prebid'` | +| `placement` | required | Unique placement identifier per publisher | `'1234'` | +| `query` | optional | Semicolon separated list of keywords | `'urlaub;ibiza;mallorca'` | +| `adtest` | optional | Deactivates tracking of impressions and clicks. **Should only be used for testing purposes!** | `true` | + -# Test Parameters +## Examples + +### Banner ```js var adUnits = [ { - code: 'banner', + code: "example1", mediaTypes: { banner: { sizes: [[300, 250], [300, 600]], } }, bids: [{ - bidder: 'aduptech', + bidder: "aduptech", + params: { + publisher: "prebid", + placement: "12345" + } + }] + } +]; +``` + +### Native +```js +var adUnits = [ + { + code: "example2", + mediaTypes: { + native: { + image: { + required: true, + sizes: [150, 150] + }, + title: { + required: true + }, + body: { + required: true + }, + clickUrl: { + required: true + }, + displayUrl: { + required: true + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: "aduptech", params: { - publisher: 'prebid', - placement: '12345' + publisher: "prebid", + placement: "12345" } }] } diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js index 746baa9ae35..854c65b1f22 100755 --- a/modules/advangelistsBidAdapter.js +++ b/modules/advangelistsBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +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'; @@ -11,7 +11,7 @@ const BIDDER_CODE = 'advangelists'; export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; -export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip']; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip', 'playerSize', 'context']; export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; let pubid = ''; @@ -56,7 +56,7 @@ export const spec = { interpretResponse(serverResponse, {bidRequest}) { let response = serverResponse.body; - if (response !== null && utils.isEmpty(response) == false) { + if (response !== null && isEmpty(response) == false) { if (isVideoBid(bidRequest)) { let bidResponse = { requestId: response.id, @@ -66,6 +66,7 @@ export const spec = { height: response.seatbid[0].bid[0].h, ttl: response.seatbid[0].bid[0].ttl || 60, creativeId: response.seatbid[0].bid[0].crid, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, currency: response.cur, mediaType: VIDEO, netRevenue: true @@ -92,6 +93,7 @@ export const spec = { ttl: response.seatbid[0].bid[0].ttl || 60, creativeId: response.seatbid[0].bid[0].crid, currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, mediaType: BANNER, netRevenue: true } @@ -101,11 +103,21 @@ export const spec = { }; function isBannerBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); + return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); } function isVideoBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.video'); + return deepAccess(bid, 'mediaTypes.video'); +} + +function getBannerBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {}; + return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); +} + +function getVideoBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'video', size: '*' }) : {}; + return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); } function isVideoBidValid(bid) { @@ -117,11 +129,11 @@ function isBannerBidValid(bid) { } function getVideoBidParam(bid, key) { - return utils.deepAccess(bid, 'params.video.' + key) || utils.deepAccess(bid, 'params.' + key); + return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); } function getBannerBidParam(bid, key) { - return utils.deepAccess(bid, 'params.banner.' + key) || utils.deepAccess(bid, 'params.' + key); + return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key); } function isMobile() { @@ -172,7 +184,7 @@ function getFirstSize(sizes) { } function parseSizes(sizes) { - return utils.parseSizesInput(sizes).map(size => { + return parseSizesInput(sizes).map(size => { let [ width, height ] = size.split('x'); return { w: parseInt(width, 10) || undefined, @@ -182,11 +194,11 @@ function parseSizes(sizes) { } function getVideoSizes(bid) { - return parseSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); + return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); } function getBannerSizes(bid) { - return parseSizes(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); + return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); } function getTopWindowReferrer() { @@ -198,12 +210,19 @@ function getTopWindowReferrer() { } function getVideoTargetingParams(bid) { - return Object.keys(Object(bid.params.video)) - .filter(param => includes(VIDEO_TARGETING, param)) - .reduce((obj, param) => { - obj[ param ] = bid.params.video[ param ]; - return obj; - }, {}); + const result = {}; + const excludeProps = ['playerSize', 'context', 'w', 'h']; + Object.keys(Object(bid.mediaTypes.video)) + .filter(key => !includes(excludeProps, 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 ]; + }); + return result; } function createVideoRequestData(bid, bidderRequest) { @@ -212,7 +231,7 @@ function createVideoRequestData(bid, bidderRequest) { let sizes = getVideoSizes(bid); let firstSize = getFirstSize(sizes); - + let bidfloor = (getVideoBidFloor(bid) == null || typeof getVideoBidFloor(bid) == 'undefined') ? 2 : getVideoBidFloor(bid); let video = getVideoTargetingParams(bid); const o = { 'device': { @@ -267,11 +286,11 @@ function createVideoRequestData(bid, bidderRequest) { 'displaymanager': '' + BIDDER_CODE, 'displaymanagerver': '' + ADAPTER_VERSION, 'tagId': placement, - 'bidfloor': 2.0, + 'bidfloor': bidfloor, 'bidfloorcur': 'USD', 'secure': secure, 'video': Object.assign({ - 'id': utils.generateUUID(), + 'id': generateUUID(), 'pos': 0, 'w': firstSize.w, 'h': firstSize.h, @@ -292,7 +311,7 @@ function createVideoRequestData(bid, bidderRequest) { function getTopWindowLocation(bidderRequest) { let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; - return utils.parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); + return parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); } function createBannerRequestData(bid, bidderRequest) { @@ -300,6 +319,7 @@ function createBannerRequestData(bid, bidderRequest) { let topReferrer = getTopWindowReferrer(); let sizes = getBannerSizes(bid); + let bidfloor = (getBannerBidFloor(bid) == null || typeof getBannerBidFloor(bid) == 'undefined') ? 2 : getBannerBidFloor(bid); const o = { 'device': { @@ -354,11 +374,11 @@ function createBannerRequestData(bid, bidderRequest) { 'displaymanager': '' + BIDDER_CODE, 'displaymanagerver': '' + ADAPTER_VERSION, 'tagId': placement, - 'bidfloor': 2.0, + 'bidfloor': bidfloor, 'bidfloorcur': 'USD', 'secure': secure, 'banner': { - 'id': utils.generateUUID(), + 'id': generateUUID(), 'pos': 0, 'w': size['w'], 'h': size['h'] diff --git a/modules/advangelistsBidAdapter.md b/modules/advangelistsBidAdapter.md index 1765241eaf3..04537ff677d 100755 --- a/modules/advangelistsBidAdapter.md +++ b/modules/advangelistsBidAdapter.md @@ -42,7 +42,11 @@ var videoAdUnit = { mediaTypes: { video: { playerSize : [[320, 480]], - context: 'instream' + context: 'instream', + skip: 1, + mimes : ['video/mp4', 'application/javascript'], + playbackmethod : [2,6], + maxduration: 30 } }, bids: [ @@ -50,14 +54,7 @@ var videoAdUnit = { bidder: 'advangelists', params: { pubid: '8537f00948fc37cc03c5f0f88e198a76', - placement: 1234, - video: { - id: 123, - skip: 1, - mimes : ['video/mp4', 'application/javascript'], - playbackmethod : [2,6], - maxduration: 30 - } + placement: 1234 } } ] diff --git a/modules/advenueBidAdapter.js b/modules/advenueBidAdapter.js deleted file mode 100644 index d7fa614b0a7..00000000000 --- a/modules/advenueBidAdapter.js +++ /dev/null @@ -1,86 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; - -const BIDDER_CODE = 'advenue'; -const URL_MULTI = 'https://ssp.advenuemedia.co.uk/?c=o&m=multi'; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], - - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && - bid.params && - !isNaN(bid.params.placementId) && - spec.supportedMediaTypes.indexOf(bid.params.traffic) !== -1 - ); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: (validBidRequests, bidderRequest) => { - let winTop; - try { - winTop = window.top; - winTop.location.toString(); - } catch (e) { - utils.logMessage(e); - winTop = window; - }; - - const location = bidderRequest ? new URL(bidderRequest.refererInfo.referer) : winTop.location; - const placements = []; - const request = { - 'secure': (location.protocol === 'https:') ? 1 : 0, - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - - for (let i = 0; i < validBidRequests.length; i++) { - const bid = validBidRequests[i]; - const params = bid.params; - placements.push({ - placementId: params.placementId, - bidId: bid.bidId, - sizes: bid.sizes, - traffic: params.traffic - }); - } - return { - method: 'POST', - url: URL_MULTI, - data: request - }; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: (serverResponse) => { - try { - serverResponse = serverResponse.body; - } catch (e) { - utils.logMessage(e); - }; - return serverResponse; - }, -}; - -registerBidder(spec); diff --git a/modules/advertlyBidAdapter.js b/modules/advertlyBidAdapter.js deleted file mode 100755 index 973b6dd0742..00000000000 --- a/modules/advertlyBidAdapter.js +++ /dev/null @@ -1,127 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import * as utils from '../src/utils.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import { ajax } from '../src/ajax.js'; -import {Renderer} from '../src/Renderer.js'; - -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -const BIDDER_CODE = 'advertly'; -const DOMAIN = 'https://api.advertly.com/'; -const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; - -function isBidRequestValid(bid) { - return (typeof bid.params !== 'undefined' && parseInt(utils.getValue(bid.params, 'publisherId')) > 0); -} - -function buildRequests(validBidRequests) { - return { - method: 'POST', - url: DOMAIN + 'www/admin/plugins/Prebid/getAd.php', - options: { - withCredentials: false, - crossOrigin: true - }, - data: validBidRequests, - }; -} - -function interpretResponse(serverResponse, request) { - const response = serverResponse.body; - const bidResponses = []; - var bidRequestResponses = []; - utils._each(response, function(bidAd) { - let bnd = {}; - Object.assign(bnd, bidAd); - bnd.adResponse = { - content: bidAd.vastXml, - height: bidAd.height, - width: bidAd.width - }; - bnd.ttl = config.getConfig('_bidderTimeout') - bnd.renderer = bidAd.context === 'outstream' ? createRenderer(bidAd, RENDERER_URL) : undefined; - bidResponses.push(bnd); - }); - - bidRequestResponses.push({ - function: 'saveResponses', - request: request, - response: bidResponses - }); - sendResponseToServer(bidRequestResponses); - return bidResponses; -} - -function outstreamRender(bidAd) { - bidAd.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - sizes: [bidAd.width, bidAd.height], - width: bidAd.width, - height: bidAd.height, - targetId: bidAd.adUnitCode, - adResponse: bidAd.adResponse, - rendererOptions: { - showVolume: false, - allowFullscreen: false - } - }); - }); -} - -function createRenderer(bidAd, url) { - const renderer = Renderer.install({ - id: bidAd.adUnitCode, - url: url, - loaded: false, - config: {'player_height': bidAd.height, 'player_width': bidAd.width}, - adUnitCode: bidAd.adUnitCode - }); - try { - renderer.setRender(outstreamRender); - } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); - } - return renderer; -} - -function onBidWon(bid) { - let wonBids = []; - wonBids.push(bid); - wonBids[0].function = 'onBidWon'; - sendResponseToServer(wonBids); -} - -function onTimeout(details) { - details.unshift({ 'function': 'onTimeout' }); - sendResponseToServer(details); -} - -function sendResponseToServer(data) { - ajax(DOMAIN + 'www/admin/plugins/Prebid/tracking/track.php', null, JSON.stringify(data), { - withCredentials: false, - method: 'POST', - crossOrigin: true - }); -} - -function getUserSyncs(syncOptions) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: DOMAIN + 'www/admin/plugins/Prebid/userSync.php' - }]; - } -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: SUPPORTED_AD_TYPES, - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs, - onBidWon, - onTimeout -}; - -registerBidder(spec); diff --git a/modules/adxcgAnalyticsAdapter.js b/modules/adxcgAnalyticsAdapter.js index 9f514c545a1..5cd04ce13cd 100644 --- a/modules/adxcgAnalyticsAdapter.js +++ b/modules/adxcgAnalyticsAdapter.js @@ -1,8 +1,8 @@ +import { parseSizesInput, uniques, buildUrl, logError } 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 * as utils from '../src/utils.js'; /** * Analytics adapter from adxcg.com @@ -32,7 +32,7 @@ var adxcgAnalyticsAdapter = Object.assign(adapter( case CONSTANTS.EVENTS.BID_ADJUSTMENT: break; case CONSTANTS.EVENTS.BID_TIMEOUT: - adxcgAnalyticsAdapter.context.events.bidTimeout = args.map(item => item.bidder).filter(utils.uniques); + adxcgAnalyticsAdapter.context.events.bidTimeout = args.map(item => item.bidder).filter(uniques); break; case CONSTANTS.EVENTS.BIDDER_DONE: break; @@ -67,7 +67,7 @@ function mapBidRequested (bidRequests) { adUnitCode: bid.adUnitCode, bidId: bid.bidId, start: bid.startTime, - sizes: utils.parseSizesInput(bid.sizes).toString(), + sizes: parseSizesInput(bid.sizes).toString(), params: bid.params }; }), @@ -112,7 +112,7 @@ function mapBidWon (bidResponse) { } function send (data) { - let adxcgAnalyticsRequestUrl = utils.buildUrl({ + let adxcgAnalyticsRequestUrl = buildUrl({ protocol: 'https', hostname: adxcgAnalyticsAdapter.context.host, pathname: '/pbrx/v2', @@ -143,7 +143,7 @@ adxcgAnalyticsAdapter.context = {}; adxcgAnalyticsAdapter.originEnableAnalytics = adxcgAnalyticsAdapter.enableAnalytics; adxcgAnalyticsAdapter.enableAnalytics = function (config) { if (!config.options.publisherId) { - utils.logError('PublisherId option is not defined. Analytics won\'t work'); + logError('PublisherId option is not defined. Analytics won\'t work'); return; } diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index e10eeaa3302..81872100cd1 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -1,410 +1,362 @@ -import {config} from '../src/config.js' -import * as utils from '../src/utils.js' -import {registerBidder} from '../src/adapters/bidderFactory.js' -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js' -import includes from 'core-js-pure/features/array/includes.js' - -/** - * Adapter for requesting bids from adxcg.net - * updated to latest prebid repo on 2017.10.20 - * updated for gdpr compliance on 2018.05.22 -requires gdpr compliance module - * updated to pass aditional auction and impression level parameters. added pass for video targeting parameters - * updated to fix native support for image width/height and icon 2019.03.17 - * updated support for userid - pubcid,ttid 2019.05.28 - * updated to support prebid 3.0 - remove non https, move to banner.xx.sizes, remove utils.getTopWindowLocation,remove utils.getTopWindowUrl(),remove utils.getTopWindowReferrer() - * updated to support prebid 4.0 - standardized video params, updated video validation, add onBidWon, onTimeOut, use standardized getFloor - */ - -const BIDDER_CODE = 'adxcg' -const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE] -const SOURCE = 'pbjs10' -const VIDEO_TARGETING = ['id', 'minduration', 'maxduration', 'startdelay', 'skippable', 'playback_method', 'frameworks'] -const USER_PARAMS_AUCTION = ['forcedDspIds', 'forcedCampaignIds', 'forcedCreativeIds', 'gender', 'dnt', 'language'] -const USER_PARAMS_BID = ['lineparam1', 'lineparam2', 'lineparam3'] -const BIDADAPTERVERSION = 'r20210330PB40' -const DEFAULT_MIN_FLOOR = 0; +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {NATIVE, BANNER, VIDEO} from '../src/mediaTypes.js'; +import { + mergeDeep, + _map, + deepAccess, + getDNT, + parseSizesInput, + deepSetValue, + isStr, + isArray, + isPlainObject, + parseUrl, + replaceAuctionPrice, triggerPixel +} from '../src/utils.js'; +import {config} from '../src/config.js'; + +const { getConfig } = config; + +const BIDDER_CODE = 'adxcg'; +const SECURE_BID_URL = 'https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'; + +const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; +const NATIVE_PARAMS = { + title: { + id: 0, + name: 'title' + }, + icon: { + id: 2, + type: 1, + name: 'img' + }, + image: { + id: 3, + type: 3, + name: 'img' + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + }, + body: { + id: 4, + name: 'data', + type: 2 + }, + cta: { + id: 1, + type: 12, + name: 'data' + } +}; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: SUPPORTED_AD_TYPES, - - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function (bid) { - if (!bid || !bid.params) { - utils.logWarn(BIDDER_CODE + ': Missing bid parameters'); - return false - } + supportedMediaTypes: [ NATIVE, BANNER, VIDEO ], + isBidRequestValid: (bid) => { + const params = bid.params || {}; + const { adzoneid } = params; + return !!(adzoneid); + }, + buildRequests: (validBidRequests, bidderRequest) => { + let app, site; - if (!utils.isStr(bid.params.adzoneid)) { - utils.logWarn(BIDDER_CODE + ': adzoneid must be specified as a string'); - return false - } + const commonFpd = getConfig('ortb2') || {}; + let { user } = commonFpd; + + if (typeof getConfig('app') === 'object') { + app = getConfig('app') || {}; + if (commonFpd.app) { + mergeDeep(app, commonFpd.app); + } + } else { + site = getConfig('site') || {}; + if (commonFpd.site) { + mergeDeep(site, commonFpd.site); + } - if (isBannerRequest(bid)) { - const banneroAdUnit = utils.deepAccess(bid, 'mediaTypes.banner'); - if (!banneroAdUnit.sizes) { - utils.logWarn(BIDDER_CODE + ': banner sizes must be specified'); - return false; + if (!site.page) { + site.page = bidderRequest.refererInfo.referer; + site.domain = parseUrl(bidderRequest.refererInfo.referer).hostname; } } - if (isVideoRequest(bid)) { - // prebid 4.0 use standardized Video parameters - const videoAdUnit = utils.deepAccess(bid, 'mediaTypes.video'); + const device = getConfig('device') || {}; + device.w = device.w || window.innerWidth; + device.h = device.h || window.innerHeight; + device.ua = device.ua || navigator.userAgent; + device.dnt = getDNT() ? 1 : 0; + device.language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + + const tid = validBidRequests[0].transactionId; + const test = setOnAny(validBidRequests, 'params.test'); + const currency = getConfig('currency.adServerCurrency'); + const cur = currency && [ currency ]; + const eids = setOnAny(validBidRequests, 'userIdAsEids'); + const schain = setOnAny(validBidRequests, 'schain'); + + const imp = validBidRequests.map((bid, id) => { + const floorInfo = bid.getFloor ? bid.getFloor({ + currency: currency || 'USD' + }) : {}; + const bidfloor = floorInfo.floor; + const bidfloorcur = floorInfo.currency; + const { adzoneid } = bid.params; + + const imp = { + id: id + 1, + tagid: adzoneid, + secure: 1, + bidfloor, + bidfloorcur, + ext: { + } + }; - if (!Array.isArray(videoAdUnit.playerSize)) { - utils.logWarn(BIDDER_CODE + ': video playerSize must be an array of integers'); - return false; - } + const assets = _map(bid.nativeParams, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + asset.id = props.id; + let wmin, hmin, w, h; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } - if (!videoAdUnit.context) { - utils.logWarn(BIDDER_CODE + ': video context must be specified'); - return false; - } + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + w = sizes[0]; + h = sizes[1]; + } - if (!Array.isArray(videoAdUnit.mimes) || videoAdUnit.mimes.length === 0) { - utils.logWarn(BIDDER_CODE + ': video mimes must be an array of strings'); - return false; + asset[props.name] = { + len: bidParams.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } + }).filter(Boolean); + + if (assets.length) { + imp.native = { + request: JSON.stringify({assets: assets}) + }; } - if (!Array.isArray(videoAdUnit.protocols) || videoAdUnit.protocols.length === 0) { - utils.logWarn(BIDDER_CODE + ': video protocols must be an array of integers'); - return false; + const bannerParams = deepAccess(bid, 'mediaTypes.banner'); + + if (bannerParams && bannerParams.sizes) { + const sizes = parseSizesInput(bannerParams.sizes); + const format = sizes.map(size => { + const [ width, height ] = size.split('x'); + const w = parseInt(width, 10); + const h = parseInt(height, 10); + return { w, h }; + }); + + imp.banner = { + format + }; } - } - return true - }, + const videoParams = deepAccess(bid, 'mediaTypes.video'); + if (videoParams) { + imp.video = videoParams; + } - /** - * Make a server request from the list of BidRequests. - * - * an array of validBidRequests - * Info describing the request to the server. - */ - buildRequests: function (validBidRequests, bidderRequest) { - let dt = new Date(); - let ratio = window.devicePixelRatio || 1; - let iobavailable = window && window.IntersectionObserver && window.IntersectionObserverEntry && window.IntersectionObserverEntry.prototype && 'intersectionRatio' in window.IntersectionObserverEntry.prototype - - let bt = config.getConfig('bidderTimeout'); - if (window.PREBID_TIMEOUT) { - bt = Math.min(window.PREBID_TIMEOUT, bt); - } + return imp; + }); - let referrer = utils.deepAccess(bidderRequest, 'refererInfo.referer'); - let page = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); - - // add common parameters - let beaconParams = { - renderformat: 'javascript', - ver: BIDADAPTERVERSION, - secure: '1', - source: SOURCE, - uw: window.screen.width, - uh: window.screen.height, - dpr: ratio, - bt: bt, - isinframe: utils.inIframe(), - cookies: utils.checkCookieSupport() ? '1' : '0', - tz: dt.getTimezoneOffset(), - dt: utils.timestamp(), - iob: iobavailable ? '1' : '0', - pbjs: '$prebid.version$', - rndid: Math.floor(Math.random() * (999999 - 100000 + 1)) + 100000, - ref: encodeURIComponent(referrer), - url: encodeURIComponent(page) + const request = { + id: bidderRequest.auctionId, + site, + app, + user, + geo: { utcoffset: new Date().getTimezoneOffset() }, + device, + source: { tid, fd: 1 }, + ext: { + prebid: { + channel: { + name: 'pbjs', + version: '$prebid.version$' + } + } + }, + cur, + imp }; - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - beaconParams.gdpr = bidderRequest.gdprConsent.gdprApplies ? '1' : '0'; - beaconParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.pubcid'))) { - beaconParams.pubcid = validBidRequests[0].userId.pubcid; + if (test) { + request.is_debug = !!test; + request.test = 1; } - - if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.tdid'))) { - beaconParams.tdid = validBidRequests[0].userId.tdid; + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); } - if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.id5id.uid'))) { - beaconParams.id5id = validBidRequests[0].userId.id5id.uid; + if (bidderRequest.uspConsent) { + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.idl_env'))) { - beaconParams.idl_env = validBidRequests[0].userId.idl_env; + if (eids) { + deepSetValue(request, 'user.ext.eids', eids); } - let biddercustom = config.getConfig(BIDDER_CODE); - if (biddercustom) { - Object.keys(biddercustom) - .filter(param => includes(USER_PARAMS_AUCTION, param)) - .forEach(param => beaconParams[param] = encodeURIComponent(biddercustom[param])) + if (schain) { + deepSetValue(request, 'source.ext.schain', schain); } - // per impression parameters - let adZoneIds = []; - let prebidBidIds = []; - let sizes = []; - let bidfloors = []; - - validBidRequests.forEach((bid, index) => { - adZoneIds.push(utils.getBidIdParameter('adzoneid', bid.params)); - prebidBidIds.push(bid.bidId); - - let bidfloor = getFloor(bid); - bidfloors.push(bidfloor); - - // copy all custom parameters impression level parameters not supported above - let customBidParams = utils.getBidIdParameter('custom', bid.params) || {} - if (customBidParams) { - Object.keys(customBidParams) - .filter(param => includes(USER_PARAMS_BID, param)) - .forEach(param => beaconParams[param + '.' + index] = encodeURIComponent(customBidParams[param])) - } - - if (isBannerRequest(bid)) { - sizes.push(utils.parseSizesInput(bid.mediaTypes.banner.sizes).join('|')); - } - - if (isNativeRequest(bid)) { - sizes.push('0x0'); - } - - if (isVideoRequest(bid)) { - if (bid.params.video) { - Object.keys(bid.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => beaconParams['video.' + param + '.' + index] = encodeURIComponent(bid.params.video[param])) - } - // copy video standarized params - beaconParams['video.context' + '.' + index] = utils.deepAccess(bid, 'mediaTypes.video.context'); - sizes.push(utils.parseSizesInput(bid.mediaTypes.video.playerSize).join('|')); - beaconParams['video.mimes' + '.' + index] = utils.deepAccess(bid, 'mediaTypes.video.mimes').join(','); - beaconParams['video.protocols' + '.' + index] = utils.deepAccess(bid, 'mediaTypes.video.protocols').join(','); - } - }) - - beaconParams.adzoneid = adZoneIds.join(','); - beaconParams.format = sizes.join(','); - beaconParams.prebidBidIds = prebidBidIds.join(','); - beaconParams.bidfloors = bidfloors.join(','); - - let adxcgRequestUrl = utils.buildUrl({ - protocol: 'https', - hostname: 'hbps.adxcg.net', - pathname: '/get/adi', - search: beaconParams - }); - - utils.logMessage(`calling adi adxcg`); return { - contentType: 'text/plain', - method: 'GET', - url: adxcgRequestUrl, - withCredentials: true + method: 'POST', + url: SECURE_BID_URL, + data: JSON.stringify(request), + options: { + contentType: 'application/json' + }, + bids: validBidRequests }; }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {bidRequests[]} An array of bids which were nested inside the server. - */ - interpretResponse: - function (serverResponse) { - utils.logMessage(`interpretResponse adxcg`); - let bidsAll = []; - - if (!serverResponse || !serverResponse.body || !utils.isArray(serverResponse.body.seatbid) || !serverResponse.body.seatbid.length) { - utils.logWarn(BIDDER_CODE + ': empty bid response'); - return bidsAll; - } - - serverResponse.body.seatbid.forEach((bids) => { - bids.bid.forEach((serverResponseOneItem) => { - let bid = {} - // parse general fields - bid.requestId = serverResponseOneItem.impid; - bid.cpm = serverResponseOneItem.price; - bid.creativeId = parseInt(serverResponseOneItem.crid); - bid.currency = serverResponseOneItem.currency ? serverResponseOneItem.currency : 'USD'; - bid.netRevenue = serverResponseOneItem.netRevenue ? serverResponseOneItem.netRevenue : true; - bid.ttl = serverResponseOneItem.ttl ? serverResponseOneItem.ttl : 300; - bid.width = serverResponseOneItem.w; - bid.height = serverResponseOneItem.h; - bid.burl = serverResponseOneItem.burl || ''; - - if (serverResponseOneItem.dealid != null && serverResponseOneItem.dealid.trim().length > 0) { - bid.dealId = serverResponseOneItem.dealid; - } + interpretResponse: function(serverResponse, { bids }) { + if (!serverResponse.body) { + return; + } + const { seatbid, cur } = serverResponse.body; + + const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { + result[bid.impid - 1] = bid; + return result; + }, []); + + return bids.map((bid, id) => { + const bidResponse = bidResponses[id]; + if (bidResponse) { + const mediaType = deepAccess(bidResponse, 'ext.crType'); + const result = { + requestId: bid.bidId, + cpm: bidResponse.price, + creativeId: bidResponse.crid, + ttl: bidResponse.ttl ? bidResponse.ttl : 300, + netRevenue: bid.netRevenue === 'net', + currency: cur, + burl: bid.burl || '', + mediaType: mediaType, + width: bidResponse.w, + height: bidResponse.h, + dealId: bidResponse.dealid, + }; + + deepSetValue(result, 'meta.mediaType', mediaType); + if (isArray(bidResponse.adomain)) { + deepSetValue(result, 'meta.advertiserDomains', bidResponse.adomain); + } - if (serverResponseOneItem.ext.crType === 'banner') { - bid.ad = serverResponseOneItem.adm; - } else if (serverResponseOneItem.ext.crType === 'video') { - bid.vastUrl = serverResponseOneItem.nurl; - bid.vastXml = serverResponseOneItem.adm; - bid.mediaType = 'video'; - } else if (serverResponseOneItem.ext.crType === 'native') { - bid.mediaType = 'native'; - bid.native = parseNative(JSON.parse(serverResponseOneItem.adm)); - } else { - utils.logWarn(BIDDER_CODE + ': unknown or undefined crType'); + if (isPlainObject(bidResponse.ext)) { + if (isStr(bidResponse.ext.mediaType)) { + deepSetValue(result, 'meta.mediaType', mediaType); } - - // prebid 4.0 meta taxonomy - if (utils.isArray(serverResponseOneItem.adomain)) { - utils.deepSetValue(bid, 'meta.advertiserDomains', serverResponseOneItem.adomain); + if (isStr(bidResponse.ext.advertiser_id)) { + deepSetValue(result, 'meta.advertiserId', bidResponse.ext.advertiser_id); } - if (utils.isArray(serverResponseOneItem.cat)) { - utils.deepSetValue(bid, 'meta.secondaryCatIds', serverResponseOneItem.cat); + if (isStr(bidResponse.ext.advertiser_name)) { + deepSetValue(result, 'meta.advertiserName', bidResponse.ext.advertiser_name); } - if (utils.isPlainObject(serverResponseOneItem.ext)) { - if (utils.isStr(serverResponseOneItem.ext.advertiser_id)) { - utils.deepSetValue(bid, 'meta.mediaType', serverResponseOneItem.ext.mediaType); - } - if (utils.isStr(serverResponseOneItem.ext.advertiser_id)) { - utils.deepSetValue(bid, 'meta.advertiserId', serverResponseOneItem.ext.advertiser_id); - } - if (utils.isStr(serverResponseOneItem.ext.advertiser_name)) { - utils.deepSetValue(bid, 'meta.advertiserName', serverResponseOneItem.ext.advertiser_name); - } - if (utils.isStr(serverResponseOneItem.ext.agency_name)) { - utils.deepSetValue(bid, 'meta.agencyName', serverResponseOneItem.ext.agency_name); - } + if (isStr(bidResponse.ext.agency_name)) { + deepSetValue(result, 'meta.agencyName', bidResponse.ext.agency_name); } - bidsAll.push(bid) - }) - }) - return bidsAll - }, + } + if (mediaType === BANNER) { + result.ad = bidResponse.adm; + } else if (mediaType === NATIVE) { + result.native = parseNative(bidResponse); + result.width = 0; + result.height = 0; + } else if (mediaType === VIDEO) { + result.vastUrl = bidResponse.nurl; + result.vastXml = bidResponse.adm; + } - onBidWon: (bid) => { - if (bid.burl) { - utils.triggerPixel(utils.replaceAuctionPrice(bid.burl, bid.originalCpm)); - } + return result; + } + }).filter(Boolean); }, + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { + const syncs = []; + let syncUrl = config.getConfig('adxcg.usersyncUrl'); + + let query = []; + if (syncOptions.pixelEnabled && syncUrl) { + if (gdprConsent) { + query.push('gdpr=' + (gdprConsent.gdprApplies & 1)); + query.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + if (uspConsent) { + query.push('us_privacy=' + encodeURIComponent(uspConsent)); + } - onTimeout(timeoutData) { - if (timeoutData == null) { - return; + syncs.push({ + type: 'image', + url: syncUrl + (query.length ? '?' + query.join('&') : '') + }); } - - let beaconParams = { - A: timeoutData.bidder, - bid: timeoutData.bidId, - a: timeoutData.adUnitCode, - cn: timeoutData.timeout, - aud: timeoutData.auctionId, - }; - let adxcgRequestUrl = utils.buildUrl({ - protocol: 'https', - hostname: 'hbps.adxcg.net', - pathname: '/event/timeout.gif', - search: beaconParams - }); - utils.logWarn(BIDDER_CODE + ': onTimeout called'); - utils.triggerPixel(adxcgRequestUrl); + return syncs; }, - - getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { - let params = ''; - if (gdprConsent && 'gdprApplies' in gdprConsent) { - if (gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - params += `?gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - } - - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: 'https://cdn.adxcg.net/pb-sync.html' + params - }]; + onBidWon: (bid) => { + // for native requests we put the nurl as an imp tracker, otherwise if the auction takes place on prebid server + // the server JS adapter puts the nurl in the adm as a tracking pixel and removes the attribute + if (bid.nurl) { + triggerPixel(replaceAuctionPrice(bid.nurl, bid.originalCpm)) } } -} +}; -function isVideoRequest(bid) { - return bid.mediaType === 'video' || !!utils.deepAccess(bid, 'mediaTypes.video'); -} +registerBidder(spec); -function isBannerRequest(bid) { - return bid.mediaType === 'banner' || !!utils.deepAccess(bid, 'mediaTypes.banner'); -} - -function isNativeRequest(bid) { - return bid.mediaType === 'native' || !!utils.deepAccess(bid, 'mediaTypes.native'); -} - -function getFloor(bid) { - if (!utils.isFn(bid.getFloor)) { - return utils.deepAccess(bid, 'params.floor', DEFAULT_MIN_FLOOR); - } - - try { - const floor = bid.getFloor({ - currency: 'EUR', - mediaType: '*', - size: '*', - bidRequest: bid - }); - return floor.floor; - } catch (e) { - utils.logWarn(BIDDER_CODE + ': call to getFloor failed:' + e.message); - return DEFAULT_MIN_FLOOR; - } -} - -function parseNative(nativeResponse) { - let bidNative = {}; - bidNative = { - clickUrl: nativeResponse.link.url, - impressionTrackers: nativeResponse.imptrackers, - clickTrackers: nativeResponse.clktrackers, - javascriptTrackers: nativeResponse.jstrackers +function parseNative(bid) { + const { assets, link, imptrackers, jstracker } = JSON.parse(bid.adm); + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined, + javascriptTrackers: jstracker ? [ jstracker ] : undefined }; - - nativeResponse.assets.forEach(asset => { - if (asset.title && asset.title.text) { - bidNative.title = asset.title.text; - } - - if (asset.img && asset.img.url) { - bidNative.image = { - url: asset.img.url, - height: asset.img.h, - width: asset.img.w - }; - } - - if (asset.icon && asset.icon.url) { - bidNative.icon = { - url: asset.icon.url, - height: asset.icon.h, - width: asset.icon.w - }; - } - - if (asset.data && asset.data.label === 'DESC' && asset.data.value) { - bidNative.body = asset.data.value; + assets.forEach(asset => { + const kind = NATIVE_ASSET_IDS[asset.id]; + const content = kind && asset[NATIVE_PARAMS[kind].name]; + if (content) { + result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; } + }); + return result; +} - if (asset.data && asset.data.label === 'SPONSORED' && asset.data.value) { - bidNative.sponsoredBy = asset.data.value; +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i], key); + if (result) { + return result; } - }) - return bidNative; + } } -registerBidder(spec) +function flatten(arr) { + return [].concat(...arr); +} diff --git a/modules/adxcgBidAdapter.md b/modules/adxcgBidAdapter.md index 8eccdb11dee..1e4ef9cd6f9 100644 --- a/modules/adxcgBidAdapter.md +++ b/modules/adxcgBidAdapter.md @@ -34,31 +34,34 @@ Module that connects to an Adxcg.com zone to fetch bids. code: 'native-ad-div', mediaTypes: { native: { - image: { + image: { sendId: false, - required: true, - sizes: [80, 80] + required: false, + sizes: [127, 83] }, icon: { - sendId: true, - }, - brand: { + sizes: [80, 80], + required: false, sendId: true, }, title: { sendId: false, - required: true, + required: false, len: 75 }, body: { sendId: false, - required: true, + required: false, len: 200 }, - sponsoredBy: { + cta: { sendId: false, required: false, - len: 20 + len: 75 + }, + sponsoredBy: { + sendId: false, + required: false } } }, @@ -73,21 +76,19 @@ Module that connects to an Adxcg.com zone to fetch bids. code: 'video-div', mediaTypes: { video: { - playerSize: [640, 480], - context: 'instream', - mimes: ['video/mp4'], - protocols: [5, 6, 8], - playback_method: ['auto_play_sound_off'] - } + playerSize: [640, 480], + context: 'instream', + mimes: ['video/mp4'], + protocols: [2, 3, 5, 6, 8], + playback_method: ['auto_play_sound_off'], + maxduration: 100, + skip: 1 + } }, bids: [{ bidder: 'adxcg', params: { - adzoneid: '20', - video: { - maxduration: 100, - skippable: true - } + adzoneid: '20' } }] } diff --git a/modules/adxpremiumAnalyticsAdapter.js b/modules/adxpremiumAnalyticsAdapter.js index f11e3b8d4e5..3e30de14052 100644 --- a/modules/adxpremiumAnalyticsAdapter.js +++ b/modules/adxpremiumAnalyticsAdapter.js @@ -1,8 +1,8 @@ +import { logError, logInfo, deepClone } 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 * as utils from '../src/utils.js'; import includes from 'core-js-pure/features/array/includes.js'; const analyticsType = 'endpoint'; @@ -95,7 +95,7 @@ function auctionInit(args) { completeObject.auction_id = args.auctionId; completeObject.publisher_id = adxpremiumAnalyticsAdapter.initOptions.pubId; - try { completeObject.referer = encodeURI(args.bidderRequests[0].refererInfo.referer.split('?')[0]); } catch (e) { utils.logError('AdxPremium Analytics - ' + e.message); } + try { completeObject.referer = encodeURI(args.bidderRequests[0].refererInfo.referer.split('?')[0]); } catch (e) { logError('AdxPremium Analytics - ' + e.message); } if (args.adUnitCodes && args.adUnitCodes.length > 0) { elementIds = args.adUnitCodes; } @@ -140,20 +140,20 @@ function bidWon(args) { if (requestDelivered) { if (completeObject.events[eventIndex]) { // do the upgrade - utils.logInfo('AdxPremium Analytics - Upgrading request'); + logInfo('AdxPremium Analytics - Upgrading request'); completeObject.events[eventIndex].is_winning = true; completeObject.events[eventIndex].is_upgrade = true; - upgradedObject = utils.deepClone(completeObject); + upgradedObject = deepClone(completeObject); upgradedObject.events = [completeObject.events[eventIndex]]; sendEvent(upgradedObject); // send upgrade } else { - utils.logInfo('AdxPremium Analytics - CANNOT FIND INDEX FOR REQUEST ' + args.requestId); + logInfo('AdxPremium Analytics - CANNOT FIND INDEX FOR REQUEST ' + args.requestId); } } else { completeObject.events[eventIndex].is_winning = true; } } else { - utils.logInfo('AdxPremium Analytics - Response not found, creating new one.'); + logInfo('AdxPremium Analytics - Response not found, creating new one.'); let tmpObject = { type: 'RESPONSE', bidder_code: args.bidderCode, @@ -167,14 +167,14 @@ function bidWon(args) { is_winning: true, is_lost: true }; - let lostObject = utils.deepClone(completeObject); + let lostObject = deepClone(completeObject); lostObject.events = [tmpObject]; sendEvent(lostObject); // send lost object } } function bidTimeout(args) { - let timeoutObject = utils.deepClone(completeObject); + let timeoutObject = deepClone(completeObject); timeoutObject.events = []; let usedRequestIds = []; @@ -191,12 +191,12 @@ function bidTimeout(args) { if (timeoutObject.events.length > 0) { sendEvent(timeoutObject); // send timeouted - utils.logInfo('AdxPremium Analytics - Sending timeouted requests'); + logInfo('AdxPremium Analytics - Sending timeouted requests'); } } function auctionEnd(args) { - utils.logInfo('AdxPremium Analytics - Auction Ended at ' + Date.now()); + logInfo('AdxPremium Analytics - Auction Ended at ' + Date.now()); if (timeoutBased) { setTimeout(function () { requestSent = true; sendEvent(completeObject); }, 3500); } else { sendEventFallback(); } } @@ -212,22 +212,22 @@ function deviceType() { } function clearSlot(elementId) { - if (includes(elementIds, elementId)) { elementIds.splice(elementIds.indexOf(elementId), 1); utils.logInfo('AdxPremium Analytics - Done with: ' + elementId); } + if (includes(elementIds, elementId)) { elementIds.splice(elementIds.indexOf(elementId), 1); logInfo('AdxPremium Analytics - Done with: ' + elementId); } if (elementIds.length == 0 && !requestSent && !timeoutBased) { requestSent = true; sendEvent(completeObject); - utils.logInfo('AdxPremium Analytics - Everything ready'); + logInfo('AdxPremium Analytics - Everything ready'); } } export function testSend() { sendEvent(completeObject); - utils.logInfo('AdxPremium Analytics - Sending without any conditions, used for testing'); + logInfo('AdxPremium Analytics - Sending without any conditions, used for testing'); } function sendEventFallback() { setTimeout(function () { - if (!requestSent) { requestSent = true; sendEvent(completeObject); utils.logInfo('AdxPremium Analytics - Sending event using fallback method.'); } + if (!requestSent) { requestSent = true; sendEvent(completeObject); logInfo('AdxPremium Analytics - Sending event using fallback method.'); } }, 2000); } @@ -241,11 +241,11 @@ function sendEvent(completeObject) { if (adxpremiumAnalyticsAdapter.initOptions.sid) { ajaxEndpoint = 'https://' + adxpremiumAnalyticsAdapter.initOptions.sid + '.adxpremium.services/graphql' } - ajax(ajaxEndpoint, function () { utils.logInfo('AdxPremium Analytics - Sending complete events at ' + Date.now()) }, dataToSend, { + ajax(ajaxEndpoint, function () { logInfo('AdxPremium Analytics - Sending complete events at ' + Date.now()) }, dataToSend, { contentType: 'application/json', method: 'POST' }); - } catch (err) { utils.logError('AdxPremium Analytics - Sending event error: ' + err); } + } catch (err) { logError('AdxPremium Analytics - Sending event error: ' + err); } } // save the base class function @@ -256,7 +256,7 @@ adxpremiumAnalyticsAdapter.enableAnalytics = function (config) { adxpremiumAnalyticsAdapter.initOptions = config.options; if (!config.options.pubId) { - utils.logError('AdxPremium Analytics - Publisher ID (pubId) option is not defined. Analytics won\'t work'); + logError('AdxPremium Analytics - Publisher ID (pubId) option is not defined. Analytics won\'t work'); return; } diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 00c7c02dc72..7e4c3a729e5 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -1,6 +1,7 @@ -import * as utils from '../src/utils.js'; +import { deepAccess, buildUrl, parseSizesInput } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; +import { createEidsArray } from './userId/eids.js'; import find from 'core-js-pure/features/array/find.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; @@ -8,6 +9,7 @@ const VERSION = '1.0'; const BIDDER_CODE = 'adyoulike'; const DEFAULT_DC = 'hb-api'; const CURRENCY = 'USD'; +const GVLID = 259; const NATIVE_IMAGE = { image: { @@ -35,6 +37,7 @@ const NATIVE_IMAGE = { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, NATIVE, VIDEO], aliases: ['ayl'], // short code /** @@ -47,7 +50,7 @@ export const spec = { const sizes = getSize(getSizeArray(bid)); const sizeValid = sizes.width > 0 && sizes.height > 0; - // allows no size fornative only + // allows no size for native only return (bid.params && bid.params.placement && (sizeValid || (bid.mediaTypes && bid.mediaTypes.native))); }, @@ -78,27 +81,40 @@ export const spec = { if (nativeReq.type === 'image') { nativeReq = Object.assign({}, NATIVE_IMAGE, nativeReq); } + // click url is always mandatory even if not specified by publisher + nativeReq.clickUrl = { + required: true + }; accumulator[bidReq.bidId].Native = nativeReq; } if (mediatype === VIDEO) { accumulator[bidReq.bidId].Video = bidReq.mediaTypes.video; + + const size = bidReq.mediaTypes.video.playerSize; + if (Array.isArray(size) && !Array.isArray(size[0])) { + accumulator[bidReq.bidId].Video.playerSize = [size]; + } } return accumulator; }, {}), PageRefreshed: getPageRefreshed() }; - if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent) { payload.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : null }; } - if (bidderRequest && bidderRequest.uspConsent) { + if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } + if (deepAccess(bidderRequest, 'userId')) { + payload.userId = createEidsArray(bidderRequest.userId); + } + const data = JSON.stringify(payload); const options = { withCredentials: true @@ -166,16 +182,17 @@ function getCanonicalUrl() { /* Get mediatype from bidRequest */ function getMediatype(bidRequest) { - var type = BANNER; - - if (utils.deepAccess(bidRequest, 'mediaTypes.native')) { - type = NATIVE; - } else if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { - type = VIDEO; + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + return BANNER; + } + if (deepAccess(bidRequest, 'mediaTypes.video')) { + return VIDEO; + } + if (deepAccess(bidRequest, 'mediaTypes.native')) { + return NATIVE; } - - return type; } + /* Get Floor price information */ function getFloor(bidRequest, size, mediaType) { const bidFloors = bidRequest.getFloor({ @@ -202,7 +219,7 @@ function getPageRefreshed() { /* Create endpoint url */ function createEndpoint(bidRequests, bidderRequest) { let host = getHostname(bidRequests); - return utils.buildUrl({ + return buildUrl({ protocol: 'https', host: `${DEFAULT_DC}${host}.omnitagjs.com`, pathname: '/hb-api/prebid/v1', @@ -239,11 +256,20 @@ function createEndpointQS(bidderRequest) { function getSizeArray(bid) { let inputSize = bid.sizes || []; + if (bid.mediaTypes && bid.mediaTypes.banner) { inputSize = bid.mediaTypes.banner.sizes || []; } - return utils.parseSizesInput(inputSize); + // handle size in bid.params in formats: [w, h] and [[w,h]]. + if (bid.params && Array.isArray(bid.params.size)) { + inputSize = bid.params.size; + if (!Array.isArray(inputSize[0])) { + inputSize = [inputSize] + } + } + + return parseSizesInput(inputSize); } /* Get parsed size from request size */ @@ -277,30 +303,31 @@ function getInternalImgUrl(uid) { function getImageUrl(config, resource, width, height) { let url = ''; - - switch (resource.Kind) { - case 'INTERNAL': - url = getInternalImgUrl(resource.Data.Internal.BlobReference.Uid); - - break; - - case 'EXTERNAL': - const dynPrefix = config.DynamicPrefix; - let extUrl = resource.Data.External.Url; - extUrl = extUrl.replace(/\[height\]/i, '' + height); - extUrl = extUrl.replace(/\[width\]/i, '' + width); - - if (extUrl.indexOf(dynPrefix) >= 0) { - const urlmatch = (/.*url=([^&]*)/gm).exec(extUrl); - url = urlmatch ? urlmatch[1] : ''; - if (!url) { - url = getInternalImgUrl((/.*key=([^&]*)/gm).exec(extUrl)[1]); + if (resource && resource.Kind) { + switch (resource.Kind) { + case 'INTERNAL': + url = getInternalImgUrl(resource.Data.Internal.BlobReference.Uid); + + break; + + case 'EXTERNAL': + const dynPrefix = config.DynamicPrefix; + let extUrl = resource.Data.External.Url; + extUrl = extUrl.replace(/\[height\]/i, '' + height); + extUrl = extUrl.replace(/\[width\]/i, '' + width); + + if (extUrl.indexOf(dynPrefix) >= 0) { + const urlmatch = (/.*url=([^&]*)/gm).exec(extUrl); + url = urlmatch ? urlmatch[1] : ''; + if (!url) { + url = getInternalImgUrl((/.*key=([^&]*)/gm).exec(extUrl)[1]); + } + } else { + url = extUrl; } - } else { - url = extUrl; - } - break; + break; + } } return url; @@ -322,13 +349,16 @@ function getTrackers(eventsArray, jsTrackers) { function getVideoAd(response) { var adJson = {}; - if (typeof response.Ad === 'string') { + if (typeof response.Ad === 'string' && response.Ad.indexOf('\/\*PREBID\*\/') > 0) { adJson = JSON.parse(response.Ad.match(/\/\*PREBID\*\/(.*)\/\*PREBID\*\//)[1]); - return utils.deepAccess(adJson, 'Content.MainVideo.Vast'); + return deepAccess(adJson, 'Content.MainVideo.Vast'); } } function getNativeAssets(response, nativeConfig) { + if (typeof response.Native === 'object') { + return response.Native; + } const native = {}; var adJson = {}; @@ -362,59 +392,61 @@ function getNativeAssets(response, nativeConfig) { } Object.keys(nativeConfig).map(function(key, index) { - if (typeof response.Native === 'object') { - native[key] = response.Native[key]; - } else { - switch (key) { - case 'title': - native[key] = textsJson.TITLE; - break; - case 'body': - native[key] = textsJson.DESCRIPTION; - break; - case 'cta': - native[key] = textsJson.CALLTOACTION; - break; - case 'sponsoredBy': - native[key] = adJson.Content.Preview.Sponsor.Name; - break; - case 'image': - // main image requested size - const imgSize = nativeConfig.image.sizes || []; - if (!imgSize.length) { - imgSize[0] = response.Width || 300; - imgSize[1] = response.Height || 250; - } + switch (key) { + case 'title': + native[key] = textsJson.TITLE; + break; + case 'body': + native[key] = textsJson.DESCRIPTION; + break; + case 'cta': + native[key] = textsJson.CALLTOACTION; + break; + case 'sponsoredBy': + native[key] = adJson.Content.Preview.Sponsor.Name; + break; + case 'image': + // main image requested size + const imgSize = nativeConfig.image.sizes || []; + if (!imgSize.length) { + imgSize[0] = response.Width || 300; + imgSize[1] = response.Height || 250; + } + const url = getImageUrl(adJson, deepAccess(adJson, 'Content.Preview.Thumbnail.Image'), imgSize[0], imgSize[1]); + if (url) { native[key] = { - url: getImageUrl(adJson, adJson.Content.Preview.Thumbnail.Image, imgSize[0], imgSize[1]), + url, width: imgSize[0], height: imgSize[1] }; - break; - case 'icon': - if (adJson.HasSponsorImage) { - // icon requested size - const iconSize = nativeConfig.icon.sizes || []; - if (!iconSize.length) { - iconSize[0] = 50; - iconSize[1] = 50; - } - - native[key] = { - url: getImageUrl(adJson, adJson.Content.Preview.Sponsor.Logo.Resource, iconSize[0], iconSize[1]), - width: iconSize[0], - height: iconSize[1] - }; - } - break; - case 'privacyIcon': - native[key] = getImageUrl(adJson, adJson.Content.Preview.Credit.Logo.Resource, 25, 25); - break; - case 'privacyLink': - native[key] = adJson.Content.Preview.Credit.Url; - break; - } + } + + break; + case 'icon': + // icon requested size + const iconSize = nativeConfig.icon.sizes || []; + if (!iconSize.length) { + iconSize[0] = 50; + iconSize[1] = 50; + } + + const icurl = getImageUrl(adJson, deepAccess(adJson, 'Content.Preview.Sponsor.Logo.Resource'), iconSize[0], iconSize[1]); + + if (icurl) { + native[key] = { + url: icurl, + width: iconSize[0], + height: iconSize[1] + }; + } + break; + case 'privacyIcon': + native[key] = getImageUrl(adJson, deepAccess(adJson, 'Content.Preview.Credit.Logo.Resource'), 25, 25); + break; + case 'privacyLink': + native[key] = deepAccess(adJson, 'Content.Preview.Credit.Url'); + break; } }); @@ -423,7 +455,7 @@ function getNativeAssets(response, nativeConfig) { /* Create bid from response */ function createBid(response, bidRequests) { - if (!response || (!response.Ad && !response.Native)) { + if (!response || (!response.Ad && !response.Native && !response.Vast)) { return } @@ -446,16 +478,19 @@ function createBid(response, bidRequests) { creativeId: response.CreativeID, cpm: response.Price, netRevenue: true, - currency: CURRENCY + currency: CURRENCY, + meta: response.Meta || { advertiserDomains: [] } }; - if (request && request.Native) { + // retreive video response if present + const vast64 = response.Vast || getVideoAd(response); + if (vast64) { + bid.vastXml = window.atob(vast64); + bid.mediaType = 'video'; + } else if (request.Native) { + // format Native response if Native was requested bid.native = getNativeAssets(response, request.Native); bid.mediaType = 'native'; - } else if (request && request.Video) { - const vast64 = response.Vast || getVideoAd(response); - bid.vastXml = vast64 ? window.atob(vast64) : ''; - bid.mediaType = 'video'; } else { bid.width = response.Width; bid.height = response.Height; diff --git a/modules/afpBidAdapter.js b/modules/afpBidAdapter.js new file mode 100644 index 00000000000..68941ff17c9 --- /dev/null +++ b/modules/afpBidAdapter.js @@ -0,0 +1,166 @@ +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' + +export const IS_DEV = location.hostname === 'localhost' +export const BIDDER_CODE = 'afp' +export const SSP_ENDPOINT = 'https://ssp.afp.ai/api/prebid' +export const REQUEST_METHOD = 'POST' +export const TEST_PAGE_URL = 'https://rtbinsight.ru/smiert-bolshikh-dannykh-kto-na-novienkogo/' +const SDK_PATH = 'https://cdn.afp.ai/ssp/sdk.js?auto_initialization=false&deploy_to_parent_window=true' +const TTL = 60 +export const IN_IMAGE_BANNER_TYPE = 'In-image' +export const IN_IMAGE_MAX_BANNER_TYPE = 'In-image Max' +export const IN_CONTENT_BANNER_TYPE = 'In-content Banner' +export const IN_CONTENT_VIDEO_TYPE = 'In-content Video' +export const OUT_CONTENT_VIDEO_TYPE = 'Out-content Video' +export const IN_CONTENT_STORY_TYPE = 'In-content Stories' +export const ACTION_SCROLLER_TYPE = 'Action Scroller' +export const ACTION_SCROLLER_LIGHT_TYPE = 'Action Scroller Light' +export const JUST_BANNER_TYPE = 'Just Banner' + +export const mediaTypeByPlaceType = { + [IN_IMAGE_BANNER_TYPE]: BANNER, + [IN_IMAGE_MAX_BANNER_TYPE]: BANNER, + [IN_CONTENT_BANNER_TYPE]: BANNER, + [IN_CONTENT_STORY_TYPE]: BANNER, + [ACTION_SCROLLER_TYPE]: BANNER, + [ACTION_SCROLLER_LIGHT_TYPE]: BANNER, + [JUST_BANNER_TYPE]: BANNER, + [IN_CONTENT_VIDEO_TYPE]: VIDEO, + [OUT_CONTENT_VIDEO_TYPE]: VIDEO, +} + +const wrapAd = (dataToCreatePlace) => { + return ` + + + + + + + + + + ` +} + +const bidRequestMap = {} + +const createRenderer = (bid, dataToCreatePlace) => { + const renderer = new Renderer({ + targetId: bid.adUnitCode, + url: SDK_PATH, + callback() { + renderer.loaded = true + window.afp.createPlaceByData(dataToCreatePlace) + } + }) + + return renderer +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid({mediaTypes, params}) { + if (typeof params !== 'object' || typeof mediaTypes !== 'object') { + return false + } + + const {placeId, placeType, imageUrl, imageWidth, imageHeight} = params + const media = mediaTypes[mediaTypeByPlaceType[placeType]] + + if (placeId && media) { + if (mediaTypeByPlaceType[placeType] === VIDEO) { + if (!media.playerSize) { + return false + } + } else if (mediaTypeByPlaceType[placeType] === BANNER) { + if (!media.sizes) { + return false + } + } + if (includes([IN_IMAGE_BANNER_TYPE, IN_IMAGE_MAX_BANNER_TYPE], placeType)) { + if (imageUrl && imageWidth && imageHeight) { + return true + } + } else { + return true + } + } + return false + }, + buildRequests(validBidRequests, {refererInfo, gdprConsent}) { + const payload = { + pageUrl: IS_DEV ? TEST_PAGE_URL : refererInfo.referer, + gdprConsent: gdprConsent, + bidRequests: validBidRequests.map(validBidRequest => { + const {bidId, transactionId, sizes, params: { + placeId, placeType, imageUrl, imageWidth, imageHeight + }} = validBidRequest + bidRequestMap[bidId] = validBidRequest + const bidRequest = { + bidId, + transactionId, + sizes, + placeId, + } + if (includes([IN_IMAGE_BANNER_TYPE, IN_IMAGE_MAX_BANNER_TYPE], placeType)) { + Object.assign(bidRequest, { + imageUrl, + imageWidth: Math.floor(imageWidth), + imageHeight: Math.floor(imageHeight), + }) + } + return bidRequest + }) + } + + return { + method: REQUEST_METHOD, + url: SSP_ENDPOINT, + data: payload, + options: { + contentType: 'application/json' + } + } + }, + interpretResponse(serverResponse) { + let bids = serverResponse.body && serverResponse.body.bids + bids = Array.isArray(bids) ? bids : [] + + return bids.map(({bidId, cpm, width, height, creativeId, currency, netRevenue, adSettings, placeSettings}, index) => { + const bid = { + requestId: bidId, + cpm, + width, + height, + creativeId, + currency, + netRevenue, + meta: { + mediaType: mediaTypeByPlaceType[placeSettings.placeType], + }, + ttl: TTL + } + + const bidRequest = bidRequestMap[bidId] + const placeContainer = bidRequest.params.placeContainer + const dataToCreatePlace = { adSettings, placeSettings, placeContainer, isPrebid: true } + + if (mediaTypeByPlaceType[placeSettings.placeType] === BANNER) { + bid.ad = wrapAd(dataToCreatePlace) + } else if (mediaTypeByPlaceType[placeSettings.placeType] === VIDEO) { + bid.vastXml = adSettings.content + bid.renderer = createRenderer(bid, dataToCreatePlace) + } + return bid + }) + } +} + +registerBidder(spec); diff --git a/modules/afpBidAdapter.md b/modules/afpBidAdapter.md new file mode 100644 index 00000000000..75ebf2bce48 --- /dev/null +++ b/modules/afpBidAdapter.md @@ -0,0 +1,348 @@ +# Overview + + +**Module Name**: AFP Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: devops@astraone.io + +# Description + +You can use this adapter to get a bid from AFP. +Please reach out to your AFP account team before using this plugin to get placeId. +The code below returns a demo ad. + +About us: https://afp.ai + +# Test Parameters +```js +var adUnits = [{ + code: 'iib-target', + mediaTypes: { + banner: { + sizes: [[0, 0]], + } + }, + bids: [{ + bidder: "afp", + params: { + placeType: "In-image", + placeId: "613221112871613d1517d181", // id from personal account + placeContainer: '#iib-container', + imageUrl: "https://rtbinsight.ru/content/images/size/w1000/2021/05/ximage-30.png.pagespeed.ic.IfuX4zAEPP.png", + imageWidth: 1000, + imageHeight: 524, + } + }] +}]; + +var adUnits = [{ + code: 'iimb-target', + mediaTypes: { + banner: { + sizes: [[0, 0]], + } + }, + bids: [{ + bidder: "afp", + params: { + placeType: "In-image Max", + placeId: "6139ae472871613d1517dedd", // id from personal account + placeContainer: '#iimb-container', + imageUrl: "https://rtbinsight.ru/content/images/size/w1000/2021/05/ximage-30.png.pagespeed.ic.IfuX4zAEPP.png", + imageWidth: 1000, + imageHeight: 524, + } + }] +}]; + +var adUnits = [{ + code: 'icb-target', + mediaTypes: { + banner: { + sizes: [[0, 0]], + } + }, + bids: [{ + bidder: "afp", + params: { + placeType: "In-content Banner", + placeId: "6139ae082871613d1517dec0", // id from personal account + placeContainer: '#icb-container', + } + }] +}]; + +var adUnits = [{ + code: 'ics-target', + mediaTypes: { + banner: { + sizes: [[0, 0]], + } + }, + bids: [{ + bidder: "afp", + params: { + placeType: "In-content Stories", + placeId: "6139ae292871613d1517ded3", // id from personal account + placeContainer: '#ics-container', + } + }] +}]; + +var adUnits = [{ + code: 'as-target', + mediaTypes: { + banner: { + sizes: [[0, 0]], + } + }, + bids: [{ + bidder: "afp", + params: { + placeType: "Action Scroller", + placeId: "6139adc12871613d1517deb0", // id from personal account + placeContainer: '#as-container', + } + }] +}]; + +var adUnits = [{ + code: 'asl-target', + mediaTypes: { + banner: { + sizes: [[0, 0]], + } + }, + bids: [{ + bidder: "afp", + params: { + placeType: "Action Scroller Light", + placeId: "6139adda2871613d1517deb8", // id from personal account + placeContainer: '#asl-container', + } + }] +}]; + +var adUnits = [{ + code: 'jb-target', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: "afp", + params: { + placeType: "Just Banner", + placeId: "6139ae832871613d1517dee9", // id from personal account + placeContainer: '#jb-container', + } + }] +}]; + +var adUnits = [{ + code: 'icv-target', + mediaTypes: { + video: { + playerSize: [[480, 320]], + } + }, + bids: [{ + bidder: "afp", + params: { + placeType: "In-content Video", + placeId: "6139ae182871613d1517deca", // id from personal account + placeContainer: '#icv-container', + } + }] +}]; + +var adUnits = [{ + code: 'ocv-target', + mediaTypes: { + video: { + playerSize: [[480, 320]], + } + }, + bids: [{ + bidder: "afp", + params: { + placeType: "Out-content Video", + placeId: "6139ae5b2871613d1517dee2", // id from personal account + placeContainer: '#ocv-container', // only the "body" tag is used as a container + } + }] +}]; +``` + +# Example page + +```html + + + + + Prebid.js In-image Example + + + + +

In-image

+
+
+ +
+ +
+ +

Just Banner

+
+
+ +
+ + + +``` +# Example page with GPT + +```html + + + + + Prebid.js In-image Example + + + + + +

In-image

+
+
+ +
+
+ +
+
+ + +``` diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js new file mode 100644 index 00000000000..f5403cca3eb --- /dev/null +++ b/modules/airgridRtdProvider.js @@ -0,0 +1,138 @@ +/** + * This module adds the AirGrid provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch real-time audience data from AirGrid + * @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'; + +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 storage = getStorageManager(AG_TCF_ID, SUBMODULE_NAME); + +/** + * Attach script tag to DOM + * @param {Object} rtdConfig + * @return {void} + */ +export function attachScriptTagToDOM(rtdConfig) { + 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) { + var p = e || 'sdk'; + var n = document.createElement('script'); + n.type = 'module'; + n.async = true; + n.src = 'https://cdn.edkt.io/' + p + '/edgekit.min.js'; + document.getElementsByTagName('head')[0].appendChild(n); + }; + edktInitializor.load(edktInitializor.accountId); + } +} + +/** + * Fetch audiences from localStorage + * @return {Array} + */ +export function getMatchedAudiencesFromStorage() { + const audiences = storage.getDataFromLocalStorage(AG_AUDIENCE_IDS_KEY); + if (!audiences) return [] + try { + return JSON.parse(audiences); + } catch (e) { + return []; + } +} + +/** + * Mutates the adUnits object + * @param {Object} adUnits + * @param {Array} audiences + * @return {void} + */ +function setAudiencesToAppNexusAdUnits(adUnits, audiences) { + adUnits.forEach((adUnit) => { + adUnit.bids.forEach((bid) => { + if (bid.bidder && bid.bidder === 'appnexus') { + deepSetValue(bid, 'params.keywords.perid', audiences || []); + } + }) + }) +} + +/** + * Pass audience data to configured bidders, using ORTB2 + * @param {Object} rtdConfig + * @param {Array} audiences + * @return {void} + */ +export function setAudiencesUsingBidderOrtb2(rtdConfig, audiences) { + const bidders = deepAccess(rtdConfig, 'params.bidders'); + if (!bidders || bidders.length === 0) return; + const allBiddersConfig = config.getBidderConfig(); + const agOrtb2 = {} + deepSetValue(agOrtb2, 'ortb2.user.ext.data.airgrid', audiences || []); + + bidders.forEach((bidder) => { + let bidderConfig = {}; + if (isPlainObject(allBiddersConfig[bidder])) { + bidderConfig = allBiddersConfig[bidder]; + } + config.setBidderConfig({ + bidders: [bidder], + config: mergeDeep(bidderConfig, agOrtb2) + }); + }); +} + +/** + * Module init + * @param {Object} rtdConfig + * @param {Object} userConsent + * @return {boolean} + */ +function init(rtdConfig, userConsent) { + attachScriptTagToDOM(rtdConfig); + return true; +} + +/** + * Real-time data retrieval from AirGrid + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} rtdConfig + * @param {Object} userConsent + * @return {void} + */ +export function passAudiencesToBidders(bidConfig, onDone, rtdConfig, userConsent) { + const adUnits = bidConfig.adUnits || getGlobal().adUnits; + const audiences = getMatchedAudiencesFromStorage(); + if (audiences.length > 0) { + setAudiencesUsingBidderOrtb2(rtdConfig, audiences); + if (adUnits) { + setAudiencesToAppNexusAdUnits(adUnits, audiences); + } + } + onDone(); +}; + +/** @type {RtdSubmodule} */ +export const airgridSubmodule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: passAudiencesToBidders +}; + +submodule(MODULE_NAME, airgridSubmodule); diff --git a/modules/airgridRtdProvider.md b/modules/airgridRtdProvider.md new file mode 100644 index 00000000000..7ee502b4c10 --- /dev/null +++ b/modules/airgridRtdProvider.md @@ -0,0 +1,95 @@ + --- + 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 + +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. + +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. + +## Usage + +Compile the Halo RTD module into your Prebid build: + +`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. + +```js +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: 'airgrid', + waitForIt: true, + params: { + // These are unique values for each account. + apiKey: 'apiKey', + accountId: 'accountId', + publisherId: 'publisherId', + bidders: ['appnexus', 'pubmatic'] + } + } + ] + } + ... +} +``` + +### Parameter Descriptions + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | `String` | RTD sub module name | Always 'airgrid' | +| waitForIt | `Boolean` | Wether to delay auction for module response | Optional. Defaults to false | +| params.apiKey | `Boolean` | Publisher partner specific API key | Required | +| params.accountId | `String` | Publisher partner specific account ID | Required | +| params.publisherId | `String` | Publisher partner specific publisher ID | Required | +| params.bidders | `Array` | Bidders with which to share segment information | Optional | + +_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) + +## Testing + +To view an example of the on page setup required: + +```bash +gulp serve-fast --modules=rtdModule,airgridRtdProvider,appnexusBidAdapter +``` + +Then in your browser access: + +``` +http://localhost:9999/integrationExamples/gpt/airgridRtdProvider_example.html +``` + +Run the unit tests, just on the AirGrid RTD module test file: + +```bash +gulp test --file "test/spec/modules/airgridRtdProvider_spec.js" +``` + +## Support + +If you require further assistance or are interested in discussing the module functionality please reach out to: +- [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). + +Happy Coding! 😊 +The AirGrid Team. diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index ce4196fe249..a9364a7a05f 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -1,5 +1,5 @@ +import { getBidIdParameter, tryAppendQueryString, createTrackPixelHtml, logError, logWarn } from '../src/utils.js'; import { Renderer } from '../src/Renderer.js'; -import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js'; @@ -16,23 +16,48 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [VIDEO, BANNER, NATIVE], - isBidRequestValid: function(bid) { - return !!(bid.params.asi); + /** + * Determines whether or not the given bid has all the params needed to make a valid request. + * + * @param {BidRequest} bidRequest + * @returns {boolean} + */ + isBidRequestValid: function(bidRequest) { + return !!(bidRequest.params.asi); }, + /** + * Build the request to the Server which requests Bids for the given array of Requests. + * Each BidRequest in the argument array is guaranteed to have passed the isBidRequestValid() test. + * + * @param {BidRequest[]} validBidRequests + * @param {*} bidderRequest + * @returns {ServerRequest|ServerRequest[]} + */ buildRequests: function(validBidRequests, bidderRequest) { - var bidRequests = []; - for (var i = 0, len = validBidRequests.length; i < len; i++) { - var bid = validBidRequests[i]; - var queryString = ''; - const asi = utils.getBidIdParameter('asi', bid.params); - queryString = utils.tryAppendQueryString(queryString, 'asi', asi); - queryString = utils.tryAppendQueryString(queryString, 'skt', SDK_TYPE); - queryString = utils.tryAppendQueryString(queryString, 'prebid_id', bid.bidId); - queryString = utils.tryAppendQueryString(queryString, 'prebid_ver', '$prebid.version$'); - - if (bidderRequest && bidderRequest.refererInfo) { - queryString = utils.tryAppendQueryString(queryString, 'page_url', bidderRequest.refererInfo.referer); + const bidRequests = []; + const pageUrl = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) || undefined; + + for (let i = 0, len = validBidRequests.length; i < len; i++) { + const bidRequest = validBidRequests[i]; + let queryString = ''; + + const asi = getBidIdParameter('asi', bidRequest.params); + queryString = tryAppendQueryString(queryString, 'asi', asi); + queryString = tryAppendQueryString(queryString, 'skt', SDK_TYPE); + queryString = tryAppendQueryString(queryString, 'tid', bidRequest.transactionId) + queryString = tryAppendQueryString(queryString, 'prebid_id', bidRequest.bidId); + queryString = tryAppendQueryString(queryString, 'prebid_ver', '$prebid.version$'); + + if (pageUrl) { + queryString = tryAppendQueryString(queryString, 'page_url', pageUrl); + } + + const eids = bidRequest.userIdAsEids; + if (eids && eids.length) { + queryString = tryAppendQueryString(queryString, 'eids', JSON.stringify({ + 'eids': eids, + })) } bidRequests.push({ @@ -45,7 +70,7 @@ export const spec = { return bidRequests; }, - interpretResponse: function(bidderResponse, request) { + interpretResponse: function(bidderResponse) { const bidderResponseBody = bidderResponse.body; if (!bidderResponseBody.is_ad_return) { @@ -62,6 +87,9 @@ export const spec = { currency: ad.currency || 'USD', netRevenue: true, ttl: 300, // 5 minutes + meta: { + advertiserDomains: [] + }, } if (AD_TYPE.VIDEO === ad.ad_type) { @@ -74,6 +102,8 @@ export const spec = { adResponse: bidderResponseBody, mediaType: VIDEO }); + + Array.prototype.push.apply(bid.meta.advertiserDomains, videoAd.adomain) } else if (AD_TYPE.BANNER === ad.ad_type) { const bannerAd = bidderResponseBody.ad.banner; Object.assign(bid, { @@ -84,48 +114,54 @@ export const spec = { }); try { bannerAd.imps.forEach(impTracker => { - const tracker = utils.createTrackPixelHtml(impTracker); + const tracker = createTrackPixelHtml(impTracker); bid.ad += tracker; }); } catch (error) { - utils.logError('Error appending tracking pixel', error); + logError('Error appending tracking pixel', error); } + + Array.prototype.push.apply(bid.meta.advertiserDomains, bannerAd.adomain) } else if (AD_TYPE.NATIVE === ad.ad_type) { const nativeAds = ad.native.template_and_ads.ads; + if (nativeAds.length === 0) { + return []; + } - nativeAds.forEach(nativeAd => { - const assets = nativeAd.assets; + const nativeAd = nativeAds[0]; + const assets = nativeAd.assets; - Object.assign(bid, { - mediaType: NATIVE - }); + Object.assign(bid, { + mediaType: NATIVE + }); - bid.native = { - title: assets.title, - body: assets.description, - cta: assets.cta_text, - sponsoredBy: assets.sponsor, - clickUrl: assets.lp_link, - impressionTrackers: nativeAd.imps, - privacyLink: assets.adchoice_url, + bid.native = { + title: assets.title, + body: assets.description, + cta: assets.cta_text, + sponsoredBy: assets.sponsor, + clickUrl: assets.lp_link, + impressionTrackers: nativeAd.imps, + privacyLink: assets.adchoice_url + }; + + if (assets.img_main !== undefined) { + bid.native.image = { + url: assets.img_main, + width: parseInt(assets.img_main_width, 10), + height: parseInt(assets.img_main_height, 10) }; + } - if (assets.img_main !== undefined) { - bid.native.image = { - url: assets.img_main, - width: parseInt(assets.img_main_width, 10), - height: parseInt(assets.img_main_height, 10) - }; - } - - if (assets.img_icon !== undefined) { - bid.native.icon = { - url: assets.img_icon, - width: parseInt(assets.img_icon_width, 10), - height: parseInt(assets.img_icon_height, 10) - }; - } - }); + if (assets.img_icon !== undefined) { + bid.native.icon = { + url: assets.img_icon, + width: parseInt(assets.img_icon_width, 10), + height: parseInt(assets.img_icon_height, 10) + }; + } + + Array.prototype.push.apply(bid.meta.advertiserDomains, nativeAd.adomain) } return [bid]; @@ -171,7 +207,7 @@ function newRenderer(bidderResponse) { try { renderer.setRender(outstreamRender); } catch (err) { - utils.logWarn('Prebid Error calling setRender on newRenderer', err); + logWarn('Prebid Error calling setRender on newRenderer', err); } return renderer; @@ -179,7 +215,7 @@ function newRenderer(bidderResponse) { function outstreamRender(bid) { bid.renderer.push(() => { - window.aja_vast_player.init({ + window['aja_vast_player'].init({ vast_tag: bid.adResponse.ad.video.vtag, ad_unit_code: bid.adUnitCode, // target div id to render video width: bid.width, diff --git a/modules/akamaiDAPIdSystem.js b/modules/akamaiDAPIdSystem.js new file mode 100644 index 00000000000..5e3a607d5fd --- /dev/null +++ b/modules/akamaiDAPIdSystem.js @@ -0,0 +1,115 @@ +/** + * This module adds DAP to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/akamaiDAPIdSubmodule + * @requires module:modules/userId + */ + +import { logMessage, logError } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { uspDataHandler } from '../src/adapterManager.js'; + +const MODULE_NAME = 'akamaiDAPId'; +const STORAGE_KEY = 'akamai_dap_token'; + +export const storage = getStorageManager(); + +/** @type {Submodule} */ +export const akamaiDAPIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{dapId:string}} + */ + decode(value) { + logMessage('akamaiDAPId [decode] value=', value); + return { dapId: value }; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {ConsentData} [consentData] + * @param {SubmoduleConfig} [config] + * @returns {IdResponse|undefined} + */ + getId(config, consentData) { + const configParams = (config && config.params); + if (!configParams) { + logError('User ID - akamaiDAPId submodule requires a valid configParams'); + return; + } else if (typeof configParams.apiHostname !== 'string') { + logError('User ID - akamaiDAPId submodule requires a valid configParams.apiHostname'); + return; + } else if (typeof configParams.domain !== 'string') { + logError('User ID - akamaiDAPId submodule requires a valid configParams.domain'); + return; + } else if (typeof configParams.type !== 'string') { + logError('User ID - akamaiDAPId submodule requires a valid configParams.type'); + return; + } + const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; + const gdprConsentString = hasGdpr ? consentData.consentString : ''; + const uspConsent = uspDataHandler.getConsentData(); + if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { + logError('User ID - akamaiDAPId submodule requires consent string to call API'); + return; + } + // XXX: retrieve first-party data here if needed + let url = ''; + let postData; + let tokenName = ''; + if (configParams.apiVersion === 'v1') { + if (configParams.type.indexOf('dap-signature:') == 0) { + let parts = configParams.type.split(':'); + let v = parts[1]; + url = `https://${configParams.apiHostname}/data-activation/v1/domain/${configParams.domain}/signature?v=${v}&gdpr=${hasGdpr}&gdpr_consent=${gdprConsentString}&us_privacy=${uspConsent}`; + tokenName = 'SigToken'; + } else { + url = `https://${configParams.apiHostname}/data-activation/v1/identity/tokenize?gdpr=${hasGdpr}&gdpr_consent=${gdprConsentString}&us_privacy=${uspConsent}`; + postData = { + 'version': 1, + 'domain': configParams.domain, + 'identity': configParams.identity, + 'type': configParams.type + }; + tokenName = 'PubToken'; + } + } else { + url = `https://${configParams.apiHostname}/data-activation/x1/domain/${configParams.domain}/identity/tokenize?gdpr=${hasGdpr}&gdpr_consent=${gdprConsentString}&us_privacy=${uspConsent}`; + postData = { + 'version': configParams.apiVersion, + 'identity': configParams.identity, + 'type': configParams.type, + 'attributes': configParams.attributes + }; + tokenName = 'x1Token'; + } + + let cb = { + success: (response, request) => { + var token = (response === '') ? request.getResponseHeader('Akamai-DAP-Token') : response; + storage.setDataInLocalStorage(STORAGE_KEY, token); + }, + error: error => { + logError('akamaiDAPId [getId:ajax.error] failed to retrieve ' + tokenName, error); + } + }; + + ajax(url, cb, JSON.stringify(postData), { contentType: 'application/json' }); + + let token = storage.getDataFromLocalStorage(STORAGE_KEY); + logMessage('akamaiDAPId [getId] returning', token); + + return { id: token }; + } +}; + +submodule('userId', akamaiDAPIdSubmodule); diff --git a/modules/akamaiDAPIdSystem.md b/modules/akamaiDAPIdSystem.md new file mode 100644 index 00000000000..9b35709c3f2 --- /dev/null +++ b/modules/akamaiDAPIdSystem.md @@ -0,0 +1,48 @@ +# Akamai Data Activation Platform Audience Segment ID Targeting + +The Akamai Data Activation Platform (DAP) is a privacy-first system that protects end-user privacy by only allowing them to be targeted as part of a larger cohort. DAP views hiding individuals in large cohorts as the best mechanism to prevent unauthorized tracking. + +The integration of DAP into Prebid.JS consists of creating a UserID plugin that interacts with the DAP API. The UserID module tokenizes the end-user identity into an ephemeral, secure pseudonymization called a dapId. The dapId is then supplied to the bid-stream where the SSP partner looks up cohort membership for that token, and supplies the cohorts to the rest of the bid-stream. + +In this system, no end-user identifier is supplied to the bid-stream, only cohorts. This is a foundational privacy principle DAP is built upon. + +## Onboarding + +Please reach out to your Akamai account representative(Prebid@akamai.com) to get provisioned on the DAP platform. + +## DAP Configuration + +First, make sure to add the DAP submodule to your Prebid.js package with: + +``` +gulp build --modules=akamaiDAPIdSystem,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'akamaiDAPId', + params: { + apiHostname: '', + domain: 'your-domain.com', + type: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', + identity: ‘your@email.com’ | ‘6175551234' | ...', + apiVersion: 'v1' | 'x1', + attributes: '{ "cohorts": [ "3:14400", "5:14400", "7:0" ],"first_name": "...","last_name": "..." }' + }, + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } +}); +``` + +In order to make use of v1 APIs, "apiVersion" needs to explicitly mentioned as 'v1'. The "apiVersion" defaults to x1 if not specified. +"attributes" can be configured in x1 API only and not v1 APIs. Please ensure that the "attributes" value is in same format as shown above. + +Refer to the sample integration example present at below location +Prebid.js/integrationExamples/gpt/akamaidap_email_example.html +Prebid.js/integrationExamples/gpt/akamaidap_signature_example.html +Prebid.js/integrationExamples/gpt/akamaidap_x1_example.html diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js new file mode 100644 index 00000000000..d143a53fbf4 --- /dev/null +++ b/modules/akamaiDapRtdProvider.js @@ -0,0 +1,474 @@ +/** + * This module adds the Akamai DAP RTD provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch real-time data from DAP + * @module modules/akamaiDapRtdProvider + * @requires module:modules/realTimeData + */ +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {submodule} from '../src/hook.js'; +import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js'; + +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'dap'; + +export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments'; +export const storage = getStorageManager(null, SUBMODULE_NAME); + +/** + * Lazy merge objects. + * @param {String} target + * @param {String} source + */ +function mergeLazy(target, source) { + if (!isPlainObject(target)) { + target = {}; + } + if (!isPlainObject(source)) { + source = {}; + } + return mergeDeep(target, source); +} + +/** + * Add real-time data & merge segments. + * @param {Object} bidConfig + * @param {Object} rtd + * @param {Object} rtdConfig + */ +export function addRealTimeData(rtd) { + logInfo('DEBUG(addRealTimeData) - ENTER'); + if (isPlainObject(rtd.ortb2)) { + let ortb2 = config.getConfig('ortb2') || {}; + logMessage('DEBUG(addRealTimeData): merging original: ', ortb2); + logMessage('DEBUG(addRealTimeData): merging in: ', rtd.ortb2); + config.setConfig({ortb2: mergeLazy(ortb2, rtd.ortb2)}); + } + logInfo('DEBUG(addRealTimeData) - EXIT'); +} + +/** + * Real-time data retrieval from Audigent + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} rtdConfi + * @param {Object} userConsent + */ +export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { + logInfo('DEBUG(getRealTimeData) - ENTER'); + logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname); + logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion); + let jsonData = storage.getDataFromLocalStorage(SEGMENTS_STORAGE_KEY); + if (jsonData) { + let data = JSON.parse(jsonData); + if (data.rtd) { + addRealTimeData(data.rtd); + onDone(); + logInfo('DEBUG(getRealTimeData) - 1'); + // Don't return - ensure the data is always fresh. + } + } + + if (rtdConfig && isPlainObject(rtdConfig.params)) { + let config = { + api_hostname: rtdConfig.params.apiHostname, + api_version: rtdConfig.params.apiVersion, + domain: rtdConfig.params.domain, + segtax: rtdConfig.params.segtax + }; + let identity = { + type: rtdConfig.params.identityType + }; + let token = dapUtils.dapGetToken(config, identity, rtdConfig.params.tokenTtl); + if (token !== null) { + let membership = dapUtils.dapGetMembership(config, token); + let udSegment = dapUtils.dapMembershipToRtbSegment(membership, config); + logMessage('DEBUG(getRealTimeData) - token: ' + token + ', user.data.segment: ', udSegment); + let data = { + rtd: { + ortb2: { + user: { + data: [ + udSegment + ] + }, + site: { + ext: { + data: { + dapSAID: membership.said + } + } + } + } + } + }; + storage.setDataInLocalStorage(SEGMENTS_STORAGE_KEY, JSON.stringify(data)); + onDone(); + } + } +} + +/** + * Module init + * @param {Object} provider + * @param {Object} userConsent + * @return {boolean} + */ +function init(provider, userConsent) { + return true; +} + +/** @type {RtdSubmodule} */ +export const akamaiDapRtdSubmodule = { + name: SUBMODULE_NAME, + getBidRequestData: getRealTimeData, + init: init +}; + +submodule(MODULE_NAME, akamaiDapRtdSubmodule); + +export const dapUtils = { + + dapGetToken: function(config, identity, ttl) { + let now = Math.round(Date.now() / 1000.0); // in seconds + let storageName = 'async_dap_token'; + let token = null; + + if (ttl == 0) { + localStorage.removeItem(storageName); + } + + let item = JSON.parse(localStorage.getItem(storageName)); + if (item == null) { + item = { + expires_at: now - 1, + token: null + }; + } else { + token = item.token; + } + + if (now > item.expires_at) { + dapUtils.dapLog('Token missing or expired, fetching a new one...'); + // Trigger a refresh + let configAsync = {...config}; + dapUtils.dapTokenize(configAsync, identity, + function(token, status, xhr) { + item.expires_at = now + ttl; + item.token = token; + localStorage.setItem(storageName, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored token; expires in ' + ttl + ' seconds'); + let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); + if (deviceId100 != null) { + localStorage.setItem('dap_deviceId100', deviceId100); + dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); + } + }, + function(xhr, status, error) { + logError('ERROR(' + error + '): failed to retrieve token! ' + status); + } + ); + } + + return token; + }, + + dapGetMembership: function(config, token) { + let now = Math.round(Date.now() / 1000.0); // in seconds + let storageName = 'async_dap_membership'; + let maxTtl = 3600; // if the cached membership is older than this, return null + let membership = null; + let item = JSON.parse(localStorage.getItem(storageName)); + if (item == null || (now - item.expires_at) > maxTtl) { + item = { + expires_at: now - 1, + said: null, + cohorts: null, + attributes: null + }; + } else { + membership = { + said: item.said, + cohorts: item.cohorts, + attributes: null + }; + } + + // Always refresh the cached membership. + let configAsync = {...config}; + dapUtils.dapMembership(configAsync, token, + function(membership, status, xhr) { + item.expires_at = now + maxTtl; + item.said = membership.said; + item.cohorts = membership.cohorts; + localStorage.setItem(storageName, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored membership:'); + dapUtils.dapLog(item); + }, + function(xhr, status, error) { + logError('ERROR(' + error + '): failed to retrieve membership! ' + status); + } + ); + + return membership; + }, + + /** + * DESCRIPTION + * + * Convert a DAP membership response to an OpenRTB2 segment object suitable + * for insertion into user.data.segment or site.data.segment. + */ + dapMembershipToRtbSegment: function(membership, config) { + let segment = { + name: 'dap.akamai.com', + ext: { + 'segtax': config.segtax + }, + segment: [] + }; + if (membership != null) { + for (const i of membership.cohorts) { + segment.segment.push({ id: i }); + } + } + return segment; + }, + + dapLog: function(args) { + let css = ''; + css += 'display: inline-block;'; + css += 'color: #fff;'; + css += 'background: #F28B20;'; + css += 'padding: 1px 4px;'; + css += 'border-radius: 3px'; + + logInfo('%cDAP Client', css, args); + }, + + /******************************************************************************* + * + * V2 (And Beyond) API + * + ******************************************************************************/ + + /** + * SYNOPSIS + * + * dapTokenize( config, identity ); + * + * DESCRIPTION + * + * Tokenize an identity into a secure, privacy safe pseudonymiziation. + * + * PARAMETERS + * + * config: an array of system configuration parameters + * + * identity: an array of identity parameters passed to the tokenizing system + * + * EXAMPLE + * + * config = { + * api_hostname: "prebid.dap.akadns.net", // required + * domain: "prebid.org", // required + * api_version: "x1", // optional, default "x1" + * }; + * + * token = null; + * identity_email = { + * type: "email", + * identity: "obiwan@jedi.com" + * attributes: { cohorts: [ "100:1641013200", "101:1641013200", "102":3:1641013200" ] }, + * }; + * dap_x1_tokenize( config, identity_email, + * function( response, status, xhr ) { token = response; }, + * function( xhr, status, error ) { ; } // handle error + * + * token = null; + * identity_signature = { type: "signature:1.0.0" }; + * dap_x1_tokenize( config, identity_signature, + * function( response, status, xhr } { token = response; }, + * function( xhr, status, error ) { ; } // handle error + */ + dapTokenize: function(config, identity, onSuccess = null, onError = null) { + if (onError == null) { + onError = function(xhr, status, error) {}; + } + + if (config == null || typeof (config) == typeof (undefined)) { + onError(null, 'Invalid config object', 'ClientError'); + return; + } + + if (typeof (config.domain) != 'string') { + onError(null, 'Invalid config.domain: must be a string', 'ClientError'); + return; + } + + if (config.domain.length <= 0) { + onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError'); + return; + } + + if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { + config.api_version = 'x1'; + } + + if (typeof (config.api_version) != 'string') { + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + return; + } + + if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + return; + } + + if (identity == null || typeof (identity) == typeof (undefined)) { + onError(null, 'Invalid identity object', 'ClientError'); + return; + } + + if (!('type' in identity) || typeof (identity.type) != 'string' || identity.type.length <= 0) { + onError(null, "Identity must contain a valid 'type' field", 'ClientError'); + return; + } + + let apiParams = { + 'type': identity.type, + }; + + if (typeof (identity.identity) != typeof (undefined)) { + apiParams.identity = identity.identity; + } + if (typeof (identity.attributes) != typeof (undefined)) { + apiParams.attributes = identity.attributes; + } + + let method; + let body; + let path; + switch (config.api_version) { + case 'x1': + case 'x1-dev': + method = 'POST'; + path = '/data-activation/' + config.api_version + '/domain/' + config.domain + '/identity/tokenize'; + body = JSON.stringify(apiParams); + break; + default: + onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError'); + return; + } + + let url = 'https://' + config.api_hostname + path; + let cb = { + success: (response, request) => { + let token = null; + switch (config.api_version) { + case 'x1': + case 'x1-dev': + token = request.getResponseHeader('Akamai-DAP-Token'); + break; + } + onSuccess(token, request.status, request); + }, + error: (request, error) => { + onError(request, request.statusText, error); + } + }; + + ajax(url, cb, body, { + method: method, + customHeaders: { + 'Content-Type': 'application/json', + 'Pragma': 'akamai-x-cache-on' + } + }); + }, + + /** + * SYNOPSIS + * + * dapMembership( config, token, onSuccess, onError ); + * + * DESCRIPTION + * + * Return the audience segment membership along with a new Secure Advertising + * ID for this token. + * + * PARAMETERS + * + * config: an array of system configuration parameters + * + * token: the token previously returned from the tokenize API + * + * EXAMPLE + * + * config = { + * api_hostname: 'api.dap.akadns.net', + * }; + * + * // token from dap_x1_tokenize + * + * dapMembership( config, token, + * function( membership, status, xhr ) { + * // Run auction with membership.segments and membership.said + * }, + * function( xhr, status, error ) { + * // error + * } ); + * + */ + dapMembership: function(config, token, onSuccess = null, onError = null) { + if (onError == null) { + onError = function(xhr, status, error) {}; + } + + if (config == null || typeof (config) == typeof (undefined)) { + onError(null, 'Invalid config object', 'ClientError'); + return; + } + + if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { + config.api_version = 'x1'; + } + + if (typeof (config.api_version) != 'string') { + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + return; + } + + if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + return; + } + + if (token == null || typeof (token) != 'string') { + onError(null, 'Invalid token: must be a non-null string', 'ClientError'); + return; + } + let path = '/data-activation/' + + config.api_version + + '/token/' + token + + '/membership'; + + let url = 'https://' + config.api_hostname + path; + + let cb = { + success: (response, request) => { + onSuccess(JSON.parse(response), request.status, request); + }, + error: (error, request) => { + onError(request, request.status, error); + } + }; + + ajax(url, cb, undefined, { + method: 'GET', + customHeaders: {} + }); + } +} diff --git a/modules/akamaiDapRtdProvider.md b/modules/akamaiDapRtdProvider.md new file mode 100644 index 00000000000..ade11b88602 --- /dev/null +++ b/modules/akamaiDapRtdProvider.md @@ -0,0 +1,47 @@ +### Overview + + Akamai DAP Real time data Provider automatically invokes the DAP APIs and submit audience segments and the SAID to the bid-stream. + +### Integration + + 1) Build the akamaiDapRTD module into the Prebid.js package with: + + ``` + gulp build --modules=akamaiDapRtdProvider,... + ``` + + 2) Use `setConfig` to instruct Prebid.js to initilaize the akamaiDapRtdProvider module, as specified below. + +### Configuration + +``` + pbjs.setConfig({ + realTimeData: { + dataProviders: [ + { + name: "dap", + waitForIt: true, + params: { + apiHostname: '', + apiVersion: "x1", + domain: 'your-domain.com', + identityType: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', + segtax: , + tokenTtl: 5, + } + } + ] + } + }); + ``` + +Please reach out to your Akamai account representative(Prebid@akamai.com) to get provisioned on the DAP platform. + + +### Testing +To view an example of available segments returned by dap: +``` +‘gulp serve --modules=rtdModule,akamaiDapRtdProvider,appnexusBidAdapter,sovrnBidAdapter’ +``` +and then point your browser at: +"http://localhost:9999/integrationExamples/gpt/akamaidap_segments_example.html" diff --git a/modules/amxIdSystem.js b/modules/amxIdSystem.js new file mode 100644 index 00000000000..28323b01188 --- /dev/null +++ b/modules/amxIdSystem.js @@ -0,0 +1,153 @@ +/** + * This module adds AMX to the User ID Module + * The {@link module:modules/userId} is required + * + * @module modules/amxIdSystem + * @requires module:modules/userId + */ +import { uspDataHandler } from '../src/adapterManager.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { deepAccess, getWindowTop, logError } from '../src/utils.js'; + +const NAME = 'amxId'; +const GVL_ID = 737; +const ID_KEY = NAME; +const version = '1.0'; +const SYNC_URL = 'https://id.a-mx.com/sync/'; +const AJAX_TIMEOUT = 300; + +function validateConfig(config) { + if (config == null || config.storage == null) { + logError(`${NAME}: config.storage is required.`); + return false; + } + + if (config.storage.type !== 'html5') { + logError( + `${NAME} only supports storage.type "html5". ${config.storage.type} was provided` + ); + return false; + } + + if ( + typeof config.storage.expires === 'number' && + config.storage.expires > 30 + ) { + logError( + `${NAME}: storage.expires must be <= 30. ${config.storage.expires} was provided` + ); + return false; + } + + return true; +} + +function handleSyncResponse(client, response, callback) { + if (response.id != null && response.id.length > 0) { + callback(response.id); + return; + } + + if (response.u == null || response.u.length === 0) { + callback(null); + return; + } + + client(response.u, { + error(e) { + logError(`${NAME} failed on ${response.u}`, e); + callback(null); + }, + success(complete) { + if (complete != null && complete.length > 0) { + const value = JSON.parse(complete); + if (value.id != null) { + callback(value.id); + return; + } + } + + logError(`${NAME} invalid value`, complete); + callback(null); + }, + }); +} + +export const amxIdSubmodule = { + /** + * @type {string} + */ + name: NAME, + + /** + * @type {string} + */ + version, + + /** + * IAB TCF Vendor ID + * @type {string} + */ + gvlid: GVL_ID, + + decode: (value) => + value != null && value.length > 0 + ? { [ID_KEY]: value } + : undefined, + + getId(config, consentData, _extant) { + if (!validateConfig(config)) { + return undefined; + } + + const consent = consentData || { gdprApplies: false, consentString: '' }; + const client = ajaxBuilder(AJAX_TIMEOUT); + const usp = uspDataHandler.getConsentData(); + const ref = getRefererInfo(); + + const params = { + tagId: deepAccess(config, 'params.tagId', ''), + ref: ref.referer, + u: ref.stack[0] || getWindowTop().location.href, + v: '$prebid.version$', + vg: '$$PREBID_GLOBAL$$', + us_privacy: usp, + gdpr: consent.gdprApplies ? 1 : 0, + gdpr_consent: consent.consentString, + }; + + const callback = (done) => + client( + SYNC_URL, + { + error(e) { + logError(`${NAME} failed to load`, e); + done(null); + }, + success(responseText) { + if (responseText != null && responseText.length > 0) { + try { + const parsed = JSON.parse(responseText); + handleSyncResponse(client, parsed, done); + return; + } catch (e) { + logError(`${NAME} invalid response`, responseText); + } + } + + done(null); + }, + }, + params, + { + method: 'GET' + } + ); + + return { callback }; + }, +}; + +submodule('userId', amxIdSubmodule); diff --git a/modules/amxIdSystem.md b/modules/amxIdSystem.md new file mode 100644 index 00000000000..9de93c761a1 --- /dev/null +++ b/modules/amxIdSystem.md @@ -0,0 +1,51 @@ +# AMX RTB ID + +For help adding this module, please contact [prebid@amxrtb.com](prebid@amxrtb.com). + +### Prebid Configuration + +You can configure this module in your `userSync.userIds[]` configuration: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [ + { + name: "amxId", + storage: { + name: "amxId", + type: "html5", + expires: 14, + }, + params: { + tagId: "cHJlYmlkLm9yZw", + }, + }, + ], + }, +}); +``` + +| Param under `userSync.userIds[]` | Scope | Type | Description | Example | +| -------------------------------- | -------- | ------ | --------------------------- | ----------------------------------------- | +| name | Required | string | ID for the amxId module | `"amxId"` | +| storage | Required | Object | Settings for amxId storage | See [storage settings](#storage-settings) | +| params | Optional | Object | Parameters for amxId module | See [params](#params) | + +### Storage Settings + +The following settings are available for the `storage` property in the `userSync.userIds[]` object: + +| Param under `storage` | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| name | Required | String | Where the ID will be stored | `"amxId"` | +| type | Required | String | This must be `"html5"` | `"html5"` | +| expires | Required | Number <= 30 | number of days until the stored ID expires. **Must be less than or equal to 30** | `14` | + +### Params + +The following options are available in the `params` property in `userSync.userIds[]`: + +| Param under `params` | Scope | Type | Description | Example | +| -------------------- | -------- | ------ | ------------------------------------------------------------------------- | ---------------- | +| tagId | Optional | String | Your AMX tagId (optional) | `cHJlYmlkLm9yZw` | diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js index 6b83c40897e..53249e92a77 100644 --- a/modules/aniviewBidAdapter.js +++ b/modules/aniviewBidAdapter.js @@ -1,6 +1,7 @@ -import { VIDEO } from '../src/mediaTypes.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; +import { logError } from '../src/utils.js'; const BIDDER_CODE = 'aniview'; const GVLID = 780; @@ -172,6 +173,22 @@ function getCpmData(xml) { } return ret; } +function buildBanner(xmlStr, bidRequest, bidResponse) { + var rendererData = JSON.stringify({ + id: bidRequest.adUnitCode, + debug: window.location.href.indexOf('pbjsDebug') >= 0, + placement: bidRequest.bidRequest.adUnitCode, + width: bidResponse.width, + height: bidResponse.height, + vastXml: xmlStr, + bid: bidResponse, + config: bidRequest.bidRequest.params.rendererConfig + }); + var playerDomain = bidRequest.bidRequest.params.playerDomain || 'player.aniview.com'; + var ad = ''; + ad += '' + return ad; +} function interpretResponse(serverResponse, bidRequest) { let bidResponses = []; if (serverResponse && serverResponse.body) { @@ -181,6 +198,10 @@ function interpretResponse(serverResponse, bidRequest) { try { let bidResponse = {}; if (bidRequest && bidRequest.data && bidRequest.data.bidId && bidRequest.data.bidId !== '') { + let mediaType = VIDEO; + if (bidRequest.bidRequest && bidRequest.bidRequest.mediaTypes && !bidRequest.bidRequest.mediaTypes[VIDEO]) { + mediaType = BANNER; + } let xmlStr = serverResponse.body; let xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); if (xml && xml.getElementsByTagName('parsererror').length == 0) { @@ -195,13 +216,26 @@ function interpretResponse(serverResponse, bidRequest) { bidResponse.creativeId = xml.getElementsByTagName('Ad') && xml.getElementsByTagName('Ad')[0] && xml.getElementsByTagName('Ad')[0].getAttribute('id') ? xml.getElementsByTagName('Ad')[0].getAttribute('id') : 'creativeId'; bidResponse.currency = cpmData.currency; bidResponse.netRevenue = true; - var blob = new Blob([xmlStr], { - type: 'application/xml' - }); - bidResponse.vastUrl = window.URL.createObjectURL(blob); - bidResponse.vastXml = xmlStr; - bidResponse.mediaType = VIDEO; - if (bidRequest.bidRequest && bidRequest.bidRequest.mediaTypes && bidRequest.bidRequest.mediaTypes.video && bidRequest.bidRequest.mediaTypes.video.context === 'outstream') { bidResponse.renderer = newRenderer(bidRequest); } + bidResponse.mediaType = mediaType; + if (mediaType === VIDEO) { + try { + var blob = new Blob([xmlStr], { + type: 'application/xml' + }); + bidResponse.vastUrl = window.URL.createObjectURL(blob); + } catch (ex) { + logError('Aniview Debug create vastXml error:\n\n' + ex); + } + bidResponse.vastXml = xmlStr; + if (bidRequest.bidRequest && bidRequest.bidRequest.mediaTypes && bidRequest.bidRequest.mediaTypes.video && bidRequest.bidRequest.mediaTypes.video.context === 'outstream') { + bidResponse.renderer = newRenderer(bidRequest); + } + } else { + bidResponse.ad = buildBanner(xmlStr, bidRequest, bidResponse); + } + bidResponse.meta = { + advertiserDomains: [] + }; bidResponses.push(bidResponse); } @@ -230,7 +264,10 @@ function getSyncData(xml, options) { if (data && data.trackers && data.trackers.length) { data = data.trackers; for (var j = 0; j < data.length; j++) { - if (typeof data[j] === 'object' && typeof data[j].url === 'string' && data[j].e === 'inventory') { + if (typeof data[j] === 'object' && + typeof data[j].url === 'string' && + (data[j].e === 'inventory' || data[j].e === 'sync') + ) { if (data[j].t == 1 && options.pixelEnabled) { ret.push({url: data[j].url, type: 'image'}); } else { @@ -272,8 +309,8 @@ function getUserSyncs(syncOptions, serverResponses) { export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['avantisvideo', 'selectmediavideo'], - supportedMediaTypes: [VIDEO], + aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo', 'didnavideo'], + supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid, buildRequests, interpretResponse, diff --git a/modules/aniviewBidAdapter.md b/modules/aniviewBidAdapter.md index cfa87bb2d28..63c91ca009a 100644 --- a/modules/aniviewBidAdapter.md +++ b/modules/aniviewBidAdapter.md @@ -10,7 +10,7 @@ Maintainer: support@aniview.com Connects to ANIVIEW Ad server for bids. -ANIVIEW bid adapter supports Video ads currently. +ANIVIEW bid adapter supports Banner and Video currently. For more information about [Aniview](http://www.aniview.com), please contact [support@aniview.com](support@aniview.com). diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index 5a5d5e6f417..c9f64ab66b0 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { isInteger, logError, isEmpty, logWarn, getUniqueIdentifierStr, _each, deepSetValue } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; @@ -38,7 +38,8 @@ const SUPPORTED_USER_ID_SOURCES = [ 'liveintent.com', 'quantcast.com', 'verizonmedia.com', - 'liveramp.com' + 'liveramp.com', + 'yahoo.com' ]; const pubapiTemplate = template`${'host'}/pubapi/3.0/${'network'}/${'placement'}/${'pageid'}/${'sizeid'}/ADTECH;v=2;cmd=bid;cors=yes;alias=${'alias'};misc=${'misc'};${'dynamicParams'}`; @@ -64,7 +65,7 @@ function template(strings, ...keys) { let dict = values[values.length - 1] || {}; let result = [strings[0]]; keys.forEach(function (key, i) { - let value = utils.isInteger(key) ? values[key] : dict[key]; + let value = isInteger(key) ? values[key] : dict[key]; result.push(value, strings[i + 1]); }); return result.join(''); @@ -86,9 +87,7 @@ function _isOneMobileBidder(bidderCode) { function _isNexageRequestPost(bid) { if (_isOneMobileBidder(bid.bidder) && bid.params.id && bid.params.imp && bid.params.imp[0]) { let imp = bid.params.imp[0]; - return imp.id && imp.tagid && - ((imp.banner && imp.banner.w && imp.banner.h) || - (imp.video && imp.video.mimes && imp.video.minduration && imp.video.maxduration)); + return imp.id && imp.tagid && imp.banner && imp.banner.w && imp.banner.h; } } @@ -149,7 +148,7 @@ export const spec = { }, interpretResponse({ body }, bidRequest) { if (!body) { - utils.logError('Empty bid response', bidRequest.bidderCode, body); + logError('Empty bid response', bidRequest.bidderCode, body); } else { let bid = this._parseBidResponse(body, bidRequest); @@ -159,7 +158,7 @@ export const spec = { } }, getUserSyncs(options, serverResponses) { - const bidResponse = !utils.isEmpty(serverResponses) && serverResponses[0].body; + const bidResponse = !isEmpty(serverResponses) && serverResponses[0].body; if (bidResponse && bidResponse.ext && bidResponse.ext.pixels) { return this.parsePixelItems(bidResponse.ext.pixels); @@ -217,7 +216,7 @@ export const spec = { let server; if (!MP_SERVER_MAP.hasOwnProperty(regionParam)) { - utils.logWarn(`Unknown region '${regionParam}' for AOL bidder.`); + logWarn(`Unknown region '${regionParam}' for AOL bidder.`); regionParam = 'us'; // Default region. } @@ -236,7 +235,7 @@ export const spec = { placement: parseInt(params.placement), pageid: params.pageId || 0, sizeid: params.sizeId || 0, - alias: params.alias || utils.getUniqueIdentifierStr(), + alias: params.alias || getUniqueIdentifierStr(), misc: new Date().getTime(), // cache busting dynamicParams: this.formatMarketplaceDynamicParams(params, consentData) })); @@ -271,15 +270,11 @@ export const spec = { formatMarketplaceDynamicParams(params = {}, consentData = {}) { let queryParams = {}; - if (params.bidFloor) { - queryParams.bidfloor = params.bidFloor; - } - Object.assign(queryParams, this.formatKeyValues(params.keyValues)); Object.assign(queryParams, this.formatConsentData(consentData)); let paramsFormatted = ''; - utils._each(queryParams, (value, key) => { + _each(queryParams, (value, key) => { paramsFormatted += `${key}=${encodeURIComponent(value)};`; }); @@ -293,7 +288,7 @@ export const spec = { Object.assign(params, this.formatConsentData(consentData)); let paramsFormatted = ''; - utils._each(params, (value, key) => { + _each(params, (value, key) => { paramsFormatted += `&${key}=${encodeURIComponent(value)}`; }); @@ -306,14 +301,14 @@ export const spec = { }; if (this.isEUConsentRequired(consentData)) { - utils.deepSetValue(openRtbObject, 'regs.ext.gdpr', NUMERIC_VALUES.TRUE); + deepSetValue(openRtbObject, 'regs.ext.gdpr', NUMERIC_VALUES.TRUE); if (consentData.gdpr.consentString) { - utils.deepSetValue(openRtbObject, 'user.ext.consent', consentData.gdpr.consentString); + deepSetValue(openRtbObject, 'user.ext.consent', consentData.gdpr.consentString); } } if (consentData.uspConsent) { - utils.deepSetValue(openRtbObject, 'regs.ext.us_privacy', consentData.uspConsent); + deepSetValue(openRtbObject, 'regs.ext.us_privacy', consentData.uspConsent); } if (typeof bid.userId === 'object') { @@ -334,7 +329,7 @@ export const spec = { formatKeyValues(keyValues) { let keyValuesHash = {}; - utils._each(keyValues, (value, key) => { + _each(keyValues, (value, key) => { keyValuesHash[`kv${key}`] = value; }); @@ -370,7 +365,7 @@ export const spec = { let tagName = item.match(tagNameRegExp)[0]; let url = item.match(srcRegExp)[2]; - if (tagName && tagName) { + if (tagName && url) { pixelsItems.push({ type: tagName === SYNC_TYPES.IMAGE.TAG ? SYNC_TYPES.IMAGE.TYPE : SYNC_TYPES.IFRAME.TYPE, url: url @@ -400,7 +395,7 @@ export const spec = { cpm = bidData.price; if (cpm === null || isNaN(cpm)) { - utils.logError('Invalid price in bid response', AOL_BIDDERS_CODES.AOL, bidData); + logError('Invalid price in bid response', AOL_BIDDERS_CODES.AOL, bidData); return; } } diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index 62ae3f54125..421eb99b4c1 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { deepAccess, isPlainObject, isArray, replaceAuctionPrice, isFn } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'apacdex'; @@ -29,19 +29,19 @@ export const spec = { if (!bid.params) { return false; } - if (!bid.params.siteId) { + if (!bid.params.siteId && !bid.params.placementId) { return false; } - if (!utils.deepAccess(bid, 'mediaTypes.banner') && !utils.deepAccess(bid, 'mediaTypes.video')) { + if (!deepAccess(bid, 'mediaTypes.banner') && !deepAccess(bid, 'mediaTypes.video')) { return false; } - if (utils.deepAccess(bid, 'mediaTypes.banner')) { // Not support multi type bids, favor banner over video - if (!utils.deepAccess(bid, 'mediaTypes.banner.sizes')) { + if (deepAccess(bid, 'mediaTypes.banner')) { // Not support multi type bids, favor banner over video + if (!deepAccess(bid, 'mediaTypes.banner.sizes')) { // sizes at the banner is required. return false; } - } else if (utils.deepAccess(bid, 'mediaTypes.video')) { - if (!utils.deepAccess(bid, 'mediaTypes.video.playerSize')) { + } else if (deepAccess(bid, 'mediaTypes.video')) { + if (!deepAccess(bid, 'mediaTypes.video.playerSize')) { // playerSize is required for instream adUnits. return false; } @@ -50,20 +50,17 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { - let siteId; let schain; let eids; let geo; let test; + let bids = []; - var bids = JSON.parse(JSON.stringify(validBidRequests)) - bidderConfig = CONFIG[bids[0].bidder]; + bidderConfig = CONFIG[validBidRequests[0].bidder]; test = config.getConfig('debug'); - bids.forEach(bidReq => { - siteId = siteId || bidReq.params.siteId; - + validBidRequests.forEach(bidReq => { if (bidReq.schain) { schain = schain || bidReq.schain } @@ -95,6 +92,13 @@ export const spec = { } bySlotTargetKey[bidReq.adUnitCode] = targetKey; bidReq.targetKey = targetKey; + + let bidFloor = getBidFloor(bidReq); + if (bidFloor) { + bidReq.bidFloor = bidFloor; + } + + bids.push(JSON.parse(JSON.stringify(bidReq))); }); const payload = {}; @@ -112,7 +116,6 @@ export const spec = { var pageUrl = _extractTopWindowUrlFromBidderRequest(bidderRequest); payload.site = {}; - payload.site.id = siteId; payload.site.page = pageUrl payload.site.referrer = _extractTopWindowReferrerFromBidderRequest(bidderRequest); payload.site.hostname = getDomain(pageUrl); @@ -146,7 +149,16 @@ export const spec = { payload.geo = geo; } - payload.bids = bids; + payload.bids = bids.map(function (bid) { + return { + params: bid.params, + mediaTypes: bid.mediaTypes, + transactionId: bid.transactionId, + sizes: bid.sizes, + bidId: bid.bidId, + bidFloor: bid.bidFloor + } + }); return { method: 'POST', @@ -158,33 +170,40 @@ export const spec = { }, interpretResponse: function (serverResponse, bidRequest) { const serverBody = serverResponse.body; - if (!serverBody || !utils.isPlainObject(serverBody)) { + if (!serverBody || !isPlainObject(serverBody)) { return []; } const serverBids = serverBody.bids; - if (!serverBids || !utils.isArray(serverBids)) { + if (!serverBids || !isArray(serverBids)) { return []; } const bidResponses = []; serverBids.forEach(bid => { + const dealId = bid.dealId || ''; const bidResponse = { requestId: bid.requestId, cpm: bid.cpm, width: bid.width, height: bid.height, creativeId: bid.creativeId, - dealId: bid.dealId, currency: bid.currency, netRevenue: bid.netRevenue, ttl: bid.ttl, mediaType: bid.mediaType }; + if (dealId.length > 0) { + bidResponse.dealId = dealId; + } if (bid.vastXml) { - bidResponse.vastXml = utils.replaceAuctionPrice(bid.vastXml, bid.cpm); + bidResponse.vastXml = replaceAuctionPrice(bid.vastXml, bid.cpm); } else { - bidResponse.ad = utils.replaceAuctionPrice(bid.ad, bid.cpm); + bidResponse.ad = replaceAuctionPrice(bid.ad, bid.cpm); + } + bidResponse.meta = {}; + if (bid.meta && bid.meta.advertiserDomains && isArray(bid.meta.advertiserDomains)) { + bidResponse.meta.advertiserDomains = bid.meta.advertiserDomains; } bidResponses.push(bidResponse); }); @@ -266,7 +285,7 @@ function _extractTopWindowUrlFromBidderRequest(bidderRequest) { if (config.getConfig('pageUrl')) { return config.getConfig('pageUrl'); } - if (utils.deepAccess(bidderRequest, 'refererInfo.referer')) { + if (deepAccess(bidderRequest, 'refererInfo.referer')) { return bidderRequest.refererInfo.referer; } @@ -284,7 +303,7 @@ function _extractTopWindowUrlFromBidderRequest(bidderRequest) { * @returns {string} */ function _extractTopWindowReferrerFromBidderRequest(bidderRequest) { - if (bidderRequest && utils.deepAccess(bidderRequest, 'refererInfo.referer')) { + if (bidderRequest && deepAccess(bidderRequest, 'refererInfo.referer')) { return bidderRequest.refererInfo.referer; } @@ -321,7 +340,7 @@ export function getDomain(pageUrl) { * @returns {boolean} */ export function validateGeoObject(geo) { - if (!utils.isPlainObject(geo)) { + if (!isPlainObject(geo)) { return false; } if (!geo.lat) { @@ -336,4 +355,26 @@ export function validateGeoObject(geo) { return true; } +/** + * Get bid floor from Price Floors Module + * + * @param {Object} bid + * @returns {float||null} + */ +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.floorPrice) ? bid.params.floorPrice : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + registerBidder(spec); diff --git a/modules/apacdexBidAdapter.md b/modules/apacdexBidAdapter.md index b88190cda94..fa8c727d709 100644 --- a/modules/apacdexBidAdapter.md +++ b/modules/apacdexBidAdapter.md @@ -11,7 +11,7 @@ Maintainer: ken@apacdex.com Connects to APAC Digital Exchange for bids. Apacdex bid adapter supports Banner and Video (Instream and Outstream) ads. -# Test Parameters +# Sample Banner Ad Unit ``` var adUnits = [ { @@ -26,6 +26,7 @@ var adUnits = [ bidder: 'apacdex', params: { siteId: 'apacdex1234', // siteId provided by Apacdex + floorPrice: 0.01, // default is 0.01 if not declared } } ] @@ -33,7 +34,7 @@ var adUnits = [ ]; ``` -# Video Test Parameters +# Sample Video Ad Unit: Instream ``` var videoAdUnit = { code: 'test-div', @@ -41,7 +42,17 @@ var videoAdUnit = { mediaTypes: { video: { playerSize: [[640, 480]], - context: 'instream' + context: "instream" + api: [2], + placement: 1, + skip: 1, + linearity: 1, + minduration: 1, + maxduration: 120, + mimes: ["video/mp4", "video/x-flv", "video/x-ms-wmv", "application/vnd.apple.mpegurl", "application/x-mpegurl", "video/3gpp", "video/mpeg", "video/ogg", "video/quicktime", "video/webm", "video/x-m4v", "video/ms-asf", video/x-msvideo"], + playbackmethod: [6], + startdelay: 0, + protocols: [1, 2, 3, 4, 5, 6] }, }, bids: [ @@ -49,8 +60,45 @@ var videoAdUnit = { bidder: 'apacdex', params: { siteId: 'apacdex1234', // siteId provided by Apacdex + floorPrice: 0.01, // default is 0.01 if not declared } } ] }; -``` \ No newline at end of file +``` +mediaTypes.video object reference to section 3.2.7 Object: Video in the OpenRTB 2.5 document +You must review all video parameters to ensure validity for your player and DSPs + +# Sample Video Ad Unit: Outstream +``` +var videoAdUnit = { + code: 'test-div', + sizes: [[410, 231]], + mediaTypes: { + video: { + playerSize: [[410, 231]], + context: "outstream" + api: [2], + placement: 5, + linearity: 1, + minduration: 1, + maxduration: 120, + mimes: ["video/mp4", "video/x-flv", "video/x-ms-wmv", "application/vnd.apple.mpegurl", "application/x-mpegurl", "video/3gpp", "video/mpeg", "video/ogg", "video/quicktime", "video/webm", "video/x-m4v", "video/ms-asf", video/x-msvideo"], + playbackmethod: [6], + startdelay: 0, + protocols: [1, 2, 3, 4, 5, 6] + }, + }, + bids: [ + { + bidder: 'apacdex', + params: { + siteId: 'apacdex1234', // siteId provided by Apacdex + floorPrice: 0.01, // default is 0.01 if not declared + } + } + ] +}; +``` +mediaTypes.video object reference to section 3.2.7 Object: Video in the OpenRTB 2.5 document +You must review all video parameters to ensure validity for your player and DSPs \ No newline at end of file diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index f3c53e6fcb3..1829323df2a 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -1,5 +1,5 @@ +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 * as utils 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'; @@ -8,12 +8,14 @@ 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'; const BIDDER_CODE = 'appnexus'; 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']; @@ -107,13 +109,13 @@ export const spec = { Object.keys(userObjBid.params.user) .filter(param => includes(USER_PARAMS, param)) .forEach((param) => { - let uparam = utils.convertCamelToUnderscore(param); - if (param === 'segments' && utils.isArray(userObjBid.params.user[param])) { + let uparam = convertCamelToUnderscore(param); + if (param === 'segments' && isArray(userObjBid.params.user[param])) { let segs = []; userObjBid.params.user[param].forEach(val => { - if (utils.isNumber(val)) { + if (isNumber(val)) { segs.push({'id': val}); - } else if (utils.isPlainObject(val)) { + } else if (isPlainObject(val)) { segs.push(val); } }); @@ -150,7 +152,7 @@ export const spec = { try { debugObj = JSON.parse(debugCookie); } catch (e) { - utils.logError('AppNexus Debug Auction Cookie Error:\n\n' + e); + logError('AppNexus Debug Auction Cookie Error:\n\n' + e); } } else { const debugBidRequest = find(bidRequests, hasDebug); @@ -200,13 +202,24 @@ export const spec = { payload.app = appIdObj; } + let auctionKeywords = config.getConfig('appnexusAuctionKeywords'); + if (isPlainObject(auctionKeywords)) { + let aucKeywords = transformBidderParamKeywords(auctionKeywords); + + if (aucKeywords.length > 0) { + aucKeywords.forEach(deleteValues); + } + + payload.keywords = aucKeywords; + } + if (config.getConfig('adpod.brandCategoryExclusion')) { payload.brand_category_uniqueness = true; } if (debugObjParams.enabled) { payload.debug = debugObjParams; - utils.logInfo('AppNexus Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4)); + logInfo('AppNexus Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4)); } if (bidderRequest && bidderRequest.gdprConsent) { @@ -215,6 +228,13 @@ export const spec = { consent_string: bidderRequest.gdprConsent.consentString, consent_required: bidderRequest.gdprConsent.gdprApplies }; + + if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { + let ac = bidderRequest.gdprConsent.addtlConsent; + // pull only the ids from the string (after the ~) and convert them to an array of ints + let acStr = ac.substring(ac.indexOf('~') + 1); + payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); + } } if (bidderRequest && bidderRequest.uspConsent) { @@ -244,10 +264,12 @@ export const spec = { if (bidRequests[0].userId) { let eids = []; - addUserId(eids, utils.deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); - addUserId(eids, utils.deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); - addUserId(eids, utils.deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); - addUserId(eids, utils.deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID'); + addUserId(eids, deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null); + addUserId(eids, deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); + addUserId(eids, deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); + addUserId(eids, deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); + addUserId(eids, deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID'); + addUserId(eids, deepAccess(bidRequests[0], `userId.uid2.id`), 'uidapi.com', 'UID2'); if (eids.length) { payload.eids = eids; @@ -274,7 +296,7 @@ export const spec = { if (!serverResponse || serverResponse.error) { let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } - utils.logError(errorMessage); + logError(errorMessage); return bids; } @@ -282,7 +304,8 @@ export const spec = { serverResponse.tags.forEach(serverBid => { const rtbBid = getRtbBid(serverBid); if (rtbBid) { - if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const cpmCheck = (bidderSettings.get(bidderRequest.bidderCode, 'allowZeroCpmBids') === true) ? rtbBid.cpm >= 0 : rtbBid.cpm > 0; + if (cpmCheck && includes(this.supportedMediaTypes, rtbBid.ad_type)) { const bid = newBid(serverBid, rtbBid, bidderRequest); bid.mediaType = parseMediaType(rtbBid); bids.push(bid); @@ -302,8 +325,8 @@ export const spec = { .replace(/

(.*)<\/h1>/gm, '\n\n===== $1 =====\n\n') // Header H1 .replace(/(.*)<\/h[2-6]>/gm, '\n\n*** $1 ***\n\n') // Headers .replace(/(<([^>]+)>)/igm, ''); // Remove any other tags - utils.logMessage('https://console.appnexus.com/docs/understanding-the-debug-auction'); - utils.logMessage(debugText); + logMessage('https://console.appnexus.com/docs/understanding-the-debug-auction'); + logMessage(debugText); } return bids; @@ -337,11 +360,11 @@ export const spec = { }, transformBidParams: function (params, isOpenRtb) { - params = utils.convertTypes({ + params = convertTypes({ 'member': 'string', 'invCode': 'string', 'placementId': 'number', - 'keywords': utils.transformBidderParamKeywords, + 'keywords': transformBidderParamKeywords, 'publisherId': 'number' }, params); @@ -354,7 +377,7 @@ export const spec = { } Object.keys(params).forEach(paramKey => { - let convertedKey = utils.convertCamelToUnderscore(paramKey); + let convertedKey = convertCamelToUnderscore(paramKey); if (convertedKey !== paramKey) { params[convertedKey] = params[paramKey]; delete params[paramKey]; @@ -377,7 +400,7 @@ export const spec = { } function isPopulatedArray(arr) { - return !!(utils.isArray(arr) && arr.length > 0); + return !!(isArray(arr) && arr.length > 0); } function deleteValues(keyPairObj) { @@ -449,9 +472,9 @@ function strIsAppnexusViewabilityScript(str) { function getAppnexusViewabilityScriptFromJsTrackers(jsTrackerArray) { let viewJsPayload; - if (utils.isStr(jsTrackerArray) && strIsAppnexusViewabilityScript(jsTrackerArray)) { + if (isStr(jsTrackerArray) && strIsAppnexusViewabilityScript(jsTrackerArray)) { viewJsPayload = jsTrackerArray; - } else if (utils.isArray(jsTrackerArray)) { + } else if (isArray(jsTrackerArray)) { for (let i = 0; i < jsTrackerArray.length; i++) { let currentJsTracker = jsTrackerArray[i]; if (strIsAppnexusViewabilityScript(currentJsTracker)) { @@ -475,7 +498,7 @@ function hasPurpose1Consent(bidderRequest) { let result = true; if (bidderRequest && bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { - result = !!(utils.deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); + result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); } } return result; @@ -493,16 +516,16 @@ function formatRequest(payload, bidderRequest) { endpointUrl = URL_SIMPLE; } - if (utils.getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { + if (getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { options.customHeaders = { 'X-Is-Test': 1 } } if (payload.tags.length > MAX_IMPS_PER_REQUEST) { - const clonedPayload = utils.deepClone(payload); + const clonedPayload = deepClone(payload); - utils.chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { + chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { clonedPayload.tags = tags; const payloadString = JSON.stringify(clonedPayload); request.push({ @@ -539,14 +562,14 @@ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { try { renderer.setRender(outstreamRender); } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); + logWarn('Prebid Error calling setRender on renderer', err); } renderer.setEventHandlers({ - impression: () => utils.logMessage('AppNexus outstream video impression event'), - loaded: () => utils.logMessage('AppNexus outstream video loaded event'), + impression: () => logMessage('AppNexus outstream video impression event'), + loaded: () => logMessage('AppNexus outstream video loaded event'), ended: () => { - utils.logMessage('AppNexus outstream renderer video event'); + logMessage('AppNexus outstream renderer video event'); document.querySelector(`#${adUnitCode}`).style.display = 'none'; } }); @@ -561,7 +584,7 @@ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { * @return Bid */ function newBid(serverBid, rtbBid, bidderRequest) { - const bidRequest = utils.getBidRequest(serverBid.uuid, [bidderRequest]); + const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); const bid = { requestId: serverBid.uuid, cpm: rtbBid.cpm, @@ -578,10 +601,35 @@ function newBid(serverBid, rtbBid, bidderRequest) { } }; + // WE DON'T FULLY SUPPORT THIS ATM - future spot for adomain code; creating a stub for 5.0 compliance + if (rtbBid.adomain) { + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [] }); + } + if (rtbBid.advertiser_id) { bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); } + // temporary function; may remove at later date if/when adserver fully supports dchain + function setupDChain(rtbBid) { + let dchain = { + ver: '1.0', + complete: 0, + nodes: [{ + bsid: rtbBid.buyer_member_id.toString() + }], + }; + + return dchain; + } + if (rtbBid.buyer_member_id) { + bid.meta = Object.assign({}, bid.meta, {dchain: setupDChain(rtbBid)}); + } + + if (rtbBid.brand_id) { + bid.meta = Object.assign({}, bid.meta, { brandId: rtbBid.brand_id }); + } + if (rtbBid.rtb.video) { // shared video properties used for all 3 contexts Object.assign(bid, { @@ -591,7 +639,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { ttl: 3600 }); - const videoContext = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); switch (videoContext) { case ADPOD: const primaryCatId = getIabSubCategory(bidRequest.bidder, rtbBid.brand_category_id); @@ -612,7 +660,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { if (rtbBid.renderer_url) { const videoBid = find(bidderRequest.bids, bid => bid.bidId === serverBid.uuid); - const rendererOptions = utils.deepAccess(videoBid, 'renderer.options'); + const rendererOptions = deepAccess(videoBid, 'renderer.options'); bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions); } break; @@ -632,7 +680,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { if (jsTrackers == undefined) { jsTrackers = jsTrackerDisarmed; - } else if (utils.isStr(jsTrackers)) { + } else if (isStr(jsTrackers)) { jsTrackers = [jsTrackers, jsTrackerDisarmed]; } else { jsTrackers.push(jsTrackerDisarmed); @@ -680,12 +728,14 @@ function newBid(serverBid, rtbBid, bidderRequest) { }); try { if (rtbBid.rtb.trackers) { - const url = rtbBid.rtb.trackers[0].impression_urls[0]; - const tracker = utils.createTrackPixelHtml(url); - bid.ad += tracker; + for (let i = 0; i < rtbBid.rtb.trackers[0].impression_urls.length; i++) { + const url = rtbBid.rtb.trackers[0].impression_urls[i]; + const tracker = createTrackPixelHtml(url); + bid.ad += tracker; + } } } catch (error) { - utils.logError('Error appending tracking pixel', error); + logError('Error appending tracking pixel', error); } } @@ -735,8 +785,8 @@ function bidToTag(bid) { if (bid.params.externalImpId) { tag.external_imp_id = bid.params.externalImpId; } - if (!utils.isEmpty(bid.params.keywords)) { - let keywords = utils.transformBidderParamKeywords(bid.params.keywords); + if (!isEmpty(bid.params.keywords)) { + let keywords = transformBidderParamKeywords(bid.params.keywords); if (keywords.length > 0) { keywords.forEach(deleteValues); @@ -744,12 +794,12 @@ function bidToTag(bid) { tag.keywords = keywords; } - let gpid = utils.deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { tag.gpid = gpid; } - if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) { + if (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`)) { tag.ad_types.push(NATIVE); if (tag.sizes.length === 0) { tag.sizes = transformSizes([1, 1]); @@ -761,8 +811,8 @@ function bidToTag(bid) { } } - const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = deepAccess(bid, 'mediaTypes.video.context'); if (videoMediaType && context === 'adpod') { tag.hb_source = 7; @@ -788,7 +838,7 @@ function bidToTag(bid) { case 'context': case 'playback_method': let type = bid.params.video[param]; - type = (utils.isArray(type)) ? type[0] : type; + type = (isArray(type)) ? type[0] : type; tag.video[param] = VIDEO_MAPPING[param][type]; break; // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks @@ -799,16 +849,61 @@ function bidToTag(bid) { } }); - if (bid.params.video.frameworks && utils.isArray(bid.params.video.frameworks)) { + if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { tag['video_frameworks'] = bid.params.video.frameworks; } } + // use IAB ORTB values if the corresponding values weren't already set by bid.params.video + if (videoMediaType) { + tag.video = tag.video || {}; + Object.keys(videoMediaType) + .filter(param => includes(VIDEO_RTB_TARGETING, param)) + .forEach(param => { + switch (param) { + case 'minduration': + case 'maxduration': + if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; + break; + case 'skip': + if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); + break; + case 'skipafter': + if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; + break; + case 'playbackmethod': + if (typeof tag.video['playback_method'] !== 'number') { + let type = videoMediaType[param]; + type = (isArray(type)) ? type[0] : type; + + // we only support iab's options 1-4 at this time. + if (type >= 1 && type <= 4) { + tag.video['playback_method'] = type; + } + } + break; + case 'api': + if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { + // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) + let apiTmp = videoMediaType[param].map(val => { + let v = (val === 4) ? 5 : (val === 5) ? 4 : val; + + if (v >= 1 && v <= 5) { + return v; + } + }).filter(v => v); + tag['video_frameworks'] = apiTmp; + } + break; + } + }); + } + if (bid.renderer) { tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); } - if (bid.params.frameworks && utils.isArray(bid.params.frameworks)) { + if (bid.params.frameworks && isArray(bid.params.frameworks)) { tag['banner_frameworks'] = bid.params.frameworks; } @@ -829,8 +924,8 @@ function transformSizes(requestSizes) { let sizes = []; let sizeObj = {}; - if (utils.isArray(requestSizes) && requestSizes.length === 2 && - !utils.isArray(requestSizes[0])) { + if (isArray(requestSizes) && requestSizes.length === 2 && + !isArray(requestSizes[0])) { sizeObj.width = parseInt(requestSizes[0], 10); sizeObj.height = parseInt(requestSizes[1], 10); sizes.push(sizeObj); @@ -884,10 +979,10 @@ function hasOmidSupport(bid) { let hasOmid = false; const bidderParams = bid.params; const videoParams = bid.params.video; - if (bidderParams.frameworks && utils.isArray(bidderParams.frameworks)) { + if (bidderParams.frameworks && isArray(bidderParams.frameworks)) { hasOmid = includes(bid.params.frameworks, 6); } - if (!hasOmid && videoParams && videoParams.frameworks && utils.isArray(videoParams.frameworks)) { + if (!hasOmid && videoParams && videoParams.frameworks && isArray(videoParams.frameworks)) { hasOmid = includes(bid.params.video.frameworks, 6); } return hasOmid; @@ -902,14 +997,14 @@ function createAdPodRequest(tags, adPodBid) { const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); - const maxDuration = utils.getMaxValueFromArray(durationRangeSec); + const maxDuration = getMaxValueFromArray(durationRangeSec); const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); - let request = utils.fill(...tagToDuplicate, numberOfPlacements); + let request = fill(...tagToDuplicate, numberOfPlacements); if (requireExactDuration) { const divider = Math.ceil(numberOfPlacements / durationRangeSec.length); - const chunked = utils.chunk(request, divider); + const chunked = chunk(request, divider); // each configured duration is set as min/maxduration for a subset of requests durationRangeSec.forEach((duration, index) => { @@ -928,7 +1023,7 @@ function createAdPodRequest(tags, adPodBid) { function getAdPodPlacementNumber(videoParams) { const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; - const minAllowedDuration = utils.getMinValueFromArray(durationRangeSec); + const minAllowedDuration = getMinValueFromArray(durationRangeSec); const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); return requireExactDuration @@ -937,7 +1032,7 @@ function getAdPodPlacementNumber(videoParams) { } function setVideoProperty(tag, key, value) { - if (utils.isEmpty(tag.video)) { tag.video = {}; } + if (isEmpty(tag.video)) { tag.video = {}; } tag.video[key] = value; } @@ -968,7 +1063,7 @@ function buildNativeRequest(params) { const isImageAsset = !!(requestKey === NATIVE_MAPPING.image.serverName || requestKey === NATIVE_MAPPING.icon.serverName); if (isImageAsset && request[requestKey].sizes) { let sizes = request[requestKey].sizes; - if (utils.isArrayOfNums(sizes) || (utils.isArray(sizes) && sizes.length > 0 && sizes.every(sz => utils.isArrayOfNums(sz)))) { + if (isArrayOfNums(sizes) || (isArray(sizes) && sizes.length > 0 && sizes.every(sz => isArrayOfNums(sz)))) { request[requestKey].sizes = transformSizes(request[requestKey].sizes); } } @@ -1047,7 +1142,7 @@ function addUserId(eids, id, source, rti) { } function getBidFloor(bid) { - if (!utils.isFn(bid.getFloor)) { + if (!isFn(bid.getFloor)) { return (bid.params.reserve) ? bid.params.reserve : null; } @@ -1056,7 +1151,7 @@ function getBidFloor(bid) { mediaType: '*', size: '*' }); - if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { return floor.floor; } return null; diff --git a/modules/appnexusBidAdapter.md b/modules/appnexusBidAdapter.md index d1f61836297..7ac70e67584 100644 --- a/modules/appnexusBidAdapter.md +++ b/modules/appnexusBidAdapter.md @@ -89,7 +89,18 @@ var adUnits = [ mediaTypes: { video: { playerSize: [[300, 250]], - context: 'outstream' + context: 'outstream', + // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. + // To note - appnexus supports additional values for our system that are not part of the ORTB spec. If you want + // to use these values, they will have to be declared in the bids[].params.video object instead using the appnexus syntax. + // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will + // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. + minduration: 1, + maxduration: 60, + skip: 0, // 1 - true, 0 - false + skipafter: 5, + playbackmethod: [2], // note - we only support options 1-4 at this time + api: [1,2,3] // note - option 6 is not supported at this time } }, bids: [ diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js index 4fb89b9c720..f2d4189f237 100644 --- a/modules/apstreamBidAdapter.js +++ b/modules/apstreamBidAdapter.js @@ -1,6 +1,6 @@ +import { generateUUID, deepAccess, createTrackPixelHtml, getDNT } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; -import * as utils from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; const CONSTANTS = { @@ -221,7 +221,7 @@ var dsuModule = (function() { } function generateDsu() { - var dsuId = utils.generateUUID(); + var dsuId = generateUUID(); var loc = location(); var dsuIdSuffix = hashWithKey(dsuId + loc.toString()); @@ -303,7 +303,7 @@ function getConsentStringFromPrebid(gdprConsentConfig) { } function getIabConsentString(bidderRequest) { - if (utils.deepAccess(bidderRequest, 'gdprConsent')) { + if (deepAccess(bidderRequest, 'gdprConsent')) { return getConsentStringFromPrebid(bidderRequest.gdprConsent); } @@ -318,7 +318,7 @@ function injectPixels(ad, pixels, scripts) { let trackedAd = ad; if (pixels) { pixels.forEach(pixel => { - const tracker = utils.createTrackPixelHtml(pixel); + const tracker = createTrackPixelHtml(pixel); trackedAd += tracker; }); } @@ -420,7 +420,7 @@ function buildRequests(bidRequests, bidderRequest) { med: encodeURIComponent(window.location.href), auid: bidderRequest.auctionId, ref: document.referrer, - dnt: utils.getDNT() ? 1 : 0, + dnt: getDNT() ? 1 : 0, sr: getScreenParams() }; diff --git a/modules/asealBidAdapter.js b/modules/asealBidAdapter.js new file mode 100644 index 00000000000..559afefa94b --- /dev/null +++ b/modules/asealBidAdapter.js @@ -0,0 +1,58 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +export const BIDDER_CODE = 'aseal'; +const SUPPORTED_AD_TYPES = [BANNER]; +export const API_ENDPOINT = 'https://tkprebid.aotter.net/prebid/adapter'; +export const HEADER_AOTTER_VERSION = 'prebid_0.0.1'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['aotter', 'trek'], + supportedMediaTypes: SUPPORTED_AD_TYPES, + + isBidRequestValid: (bid) => !!bid.params.placeUid && typeof bid.params.placeUid === 'string', + + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests.length === 0) { + return []; + } + + const clientId = + config.getConfig('aseal.clientId') || ''; + + const data = { + bids: validBidRequests, + refererInfo: bidderRequest.refererInfo, + }; + + const options = { + contentType: 'application/json', + withCredentials: true, + customHeaders: { + 'x-aotter-clientid': clientId, + 'x-aotter-version': HEADER_AOTTER_VERSION, + }, + }; + + return [{ + method: 'POST', + url: API_ENDPOINT, + data, + options, + }]; + }, + + interpretResponse: (serverResponse, bidRequest) => { + if (!Array.isArray(serverResponse.body)) { + return []; + } + + const bidResponses = serverResponse.body; + + return bidResponses; + }, +}; + +registerBidder(spec); diff --git a/modules/asealBidAdapter.md b/modules/asealBidAdapter.md new file mode 100644 index 00000000000..d13b802f736 --- /dev/null +++ b/modules/asealBidAdapter.md @@ -0,0 +1,52 @@ +# Overview + +``` +Module Name: Aseal Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech-service@aotter.net +``` + +# Description + +Module that connects to Aseal server for bids. +Supported Ad Formats: + +- Banner + +# Configuration + +Following configuration is required: + +```js +pbjs.setConfig({ + aseal: { + clientId: "YOUR_CLIENT_ID" + } +}); +``` + +# Ad Unit Example + +```js +var adUnits = [ + { + code: "banner-div", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }, + bids: [ + { + bidder: "aseal", + params: { + placeUid: "f4a74f73-9a74-4a87-91c9-545c6316c23d" + } + } + ] + } +]; +``` diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js new file mode 100644 index 00000000000..bf45b9ee48f --- /dev/null +++ b/modules/asoBidAdapter.js @@ -0,0 +1,351 @@ +import { _each, deepAccess, logWarn, tryAppendQueryString, inIframe, getWindowTop, parseUrl, parseSizesInput, isFn, getDNT, deepSetValue } from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; + +const BIDDER_CODE = 'aso'; +const DEFAULT_SERVER_URL = 'https://srv.aso1.net'; +const DEFAULT_SERVER_PATH = '/prebid/bidder'; +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +const TTL = 300; + +export const spec = { + + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: bid => { + return !!bid.params && !!bid.params.zone; + }, + + buildRequests: (validBidRequests, bidderRequest) => { + let serverRequests = []; + + _each(validBidRequests, bidRequest => { + const payload = createBasePayload(bidRequest, bidderRequest); + + const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); + const videoParams = deepAccess(bidRequest, 'mediaTypes.video'); + + let imp; + + if (bannerParams && videoParams) { + logWarn('Please note, multiple mediaTypes are not supported. The only banner will be used.') + } + + if (bannerParams) { + imp = createBannerImp(bidRequest, bannerParams) + } else if (videoParams) { + imp = createVideoImp(bidRequest, videoParams) + } + + if (imp) { + payload.imp.push(imp); + } else { + return; + } + + serverRequests.push({ + method: 'POST', + url: getEnpoint(bidRequest), + data: payload, + options: { + withCredentials: true, + crossOrigin: true + }, + bidRequest: bidRequest + }); + }); + + return serverRequests; + }, + + interpretResponse: (serverResponse, {bidRequest}) => { + const response = serverResponse && serverResponse.body; + + if (!response) { + return []; + } + + const serverBids = response.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []); + const serverBid = serverBids[0]; + + let bids = []; + + const bid = { + requestId: serverBid.impid, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + ttl: TTL, + creativeId: serverBid.crid, + netRevenue: true, + currency: response.cur, + mediaType: bidRequest.mediaType, + meta: { + mediaType: bidRequest.mediaType, + advertiserDomains: serverBid.adomain ? serverBid.adomain : [] + } + }; + + if (bid.mediaType === BANNER) { + bid.ad = serverBid.adm; + } else if (bid.mediaType === VIDEO) { + bid.vastXml = serverBid.adm; + if (deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream') { + bid.adResponse = { + content: bid.vastXml, + }; + bid.renderer = createRenderer(bidRequest, OUTSTREAM_RENDERER_URL); + } + } + + bids.push(bid); + + return bids; + }, + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const urls = []; + + if (serverResponses && serverResponses.length !== 0) { + let query = ''; + if (gdprConsent) { + query = tryAppendQueryString(query, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); + query = tryAppendQueryString(query, 'consents_str', gdprConsent.consentString); + const consentsIds = getConsentsIds(gdprConsent); + if (consentsIds) { + query = tryAppendQueryString(query, 'consents', consentsIds); + } + } + + if (uspConsent) { + query = tryAppendQueryString(query, 'us_privacy', uspConsent); + } + + _each(serverResponses, resp => { + const userSyncs = deepAccess(resp, 'body.ext.user_syncs'); + if (!userSyncs) { + return; + } + + _each(userSyncs, us => { + urls.push({ + type: us.type, + url: us.url + (query ? '?' + query : '') + }); + }); + }); + } + + return urls; + } +}; + +function outstreamRender(bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }); + }); +} + +function createRenderer(bid, url) { + const renderer = Renderer.install({ + id: bid.bidId, + url: url, + loaded: false, + config: deepAccess(bid, 'renderer.options'), + adUnitCode: bid.adUnitCode + }); + renderer.setRender(outstreamRender); + return renderer; +} + +function getUrlsInfo(bidderRequest) { + let page = ''; + let referrer = ''; + + const {refererInfo} = bidderRequest; + + if (inIframe()) { + page = refererInfo.referer; + } else { + const w = getWindowTop(); + page = w.location.href; + referrer = w.document.referrer || ''; + } + + page = config.getConfig('pageUrl') || page; + const url = parseUrl(page); + const domain = url.hostname; + + return { + domain, + page, + referrer + }; +} + +function getSize(paramSizes) { + const parsedSizes = parseSizesInput(paramSizes); + const sizes = parsedSizes.map(size => { + const [width, height] = size.split('x'); + const w = parseInt(width, 10); + const h = parseInt(height, 10); + return {w, h}; + }); + + return sizes[0] || null; +} + +function getBidFloor(bidRequest, size) { + if (!isFn(bidRequest.getFloor)) { + return null; + } + + const bidFloor = bidRequest.getFloor({ + mediaType: bidRequest.mediaType, + size: size ? [size.w, size.h] : '*' + }); + + if (!isNaN(bidFloor.floor)) { + return bidFloor; + } + + return null; +} + +function createBaseImp(bidRequest, size) { + const imp = { + id: bidRequest.bidId, + tagid: bidRequest.adUnitCode, + secure: 1 + }; + + const bidFloor = getBidFloor(bidRequest, size); + if (bidFloor !== null) { + imp.bidfloor = bidFloor.floor; + imp.bidfloorcur = bidFloor.currency; + } + + return imp; +} + +function createBannerImp(bidRequest, bannerParams) { + bidRequest.mediaType = BANNER; + + const size = getSize(bannerParams.sizes); + const imp = createBaseImp(bidRequest, size); + + imp.banner = { + w: size.w, + h: size.h, + topframe: inIframe() ? 0 : 1 + } + + return imp; +} + +function createVideoImp(bidRequest, videoParams) { + bidRequest.mediaType = VIDEO; + const size = getSize(videoParams.playerSize); + const imp = createBaseImp(bidRequest, size); + + imp.video = { + mimes: videoParams.mimes, + minduration: videoParams.minduration, + startdelay: videoParams.startdelay, + linearity: videoParams.linearity, + maxduration: videoParams.maxduration, + skip: videoParams.skip, + protocols: videoParams.protocols, + skipmin: videoParams.skipmin, + api: videoParams.api + } + + if (size) { + imp.video.w = size.w; + imp.video.h = size.h; + } + + return imp; +} + +function getEnpoint(bidRequest) { + const serverUrl = bidRequest.params.serverUrl || DEFAULT_SERVER_URL; + const serverPath = bidRequest.params.serverPath || DEFAULT_SERVER_PATH; + + return serverUrl + serverPath + '?zid=' + bidRequest.params.zone + '&pbjs=$prebid.version$'; +} + +function getConsentsIds(gdprConsent) { + const consents = deepAccess(gdprConsent, 'vendorData.purpose.consents', []); + let consentsIds = []; + + Object.keys(consents).forEach(function (key) { + if (consents[key] === true) { + consentsIds.push(key); + } + }); + + return consentsIds.join(','); +} + +function createBasePayload(bidRequest, bidderRequest) { + const urlsInfo = getUrlsInfo(bidderRequest); + + const payload = { + id: bidRequest.auctionId + '_' + bidRequest.bidId, + at: 1, + tmax: bidderRequest.timeout, + site: { + id: urlsInfo.domain, + domain: urlsInfo.domain, + page: urlsInfo.page, + ref: urlsInfo.referrer + }, + device: { + dnt: getDNT() ? 1 : 0, + h: window.innerHeight, + w: window.innerWidth, + }, + imp: [], + ext: {}, + user: {} + }; + + if (bidRequest.params.attr) { + deepSetValue(payload, 'site.ext.attr', bidRequest.params.attr); + } + + if (bidderRequest.gdprConsent) { + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + const consentsIds = getConsentsIds(bidderRequest.gdprConsent); + if (consentsIds) { + deepSetValue(payload, 'user.ext.consents', consentsIds); + } + deepSetValue(payload, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); + } + + if (bidderRequest.uspConsent) { + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (config.getConfig('coppa')) { + deepSetValue(payload, 'regs.coppa', 1); + } + + const eids = deepAccess(bidRequest, 'userIdAsEids'); + if (eids && eids.length) { + deepSetValue(payload, 'user.ext.eids', eids); + } + + return payload; +} + +registerBidder(spec); diff --git a/modules/asoBidAdapter.md b/modules/asoBidAdapter.md new file mode 100644 index 00000000000..32f4ebf5cef --- /dev/null +++ b/modules/asoBidAdapter.md @@ -0,0 +1,78 @@ +# Overview + +``` +Module Name: Adserver.Online Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@adsrv.org +``` + +# Description + +Adserver.Online Bidder Adapter for Prebid.js. + +For more information, please visit [Adserver.Online](https://adserver.online). + +# Parameters + +| Name | Scope | Description | Example | Type | +|---------------|----------|-------------------------|-----------|-----------| +| `zone` | required | Zone ID | `73815` | `Integer` | +| `attr` | optional | Custom targeting params | `{keywords: ["a", "b"]}` | `Object` | + + + +# Test parameters for banner +```js +var adUnits = [ + { + code: 'banner1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'aso', + params: { + zone: 73815 + } + } + ] + } +]; +``` + +# Test parameters for video +```js +var videoAdUnit = [ + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' // or 'outstream' + } + }, + bids: [{ + bidder: 'aso', + params: { + zone: 34668 + } + }] + } +]; +``` + +# Configuration + +The Adserver.Online Bid Adapter expects Prebid Cache (for video) to be enabled. + +``` +pbjs.setConfig({ + usePrebidCache: true, + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } +}); +``` diff --git a/modules/astraoneBidAdapter.js b/modules/astraoneBidAdapter.js index 2fec3892d27..c233e665499 100644 --- a/modules/astraoneBidAdapter.js +++ b/modules/astraoneBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js' +import { _map } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import { BANNER } from '../src/mediaTypes.js' @@ -7,7 +7,7 @@ const SSP_ENDPOINT = 'https://ssp.astraone.io/auction/prebid'; const TTL = 60; function buildBidRequests(validBidRequests) { - return utils._map(validBidRequests, function(validBidRequest) { + return _map(validBidRequests, function(validBidRequest) { const params = validBidRequest.params; const bidRequest = { bidId: validBidRequest.bidId, diff --git a/modules/atomxBidAdapter.js b/modules/atomxBidAdapter.js deleted file mode 100644 index e9f15218c4c..00000000000 --- a/modules/atomxBidAdapter.js +++ /dev/null @@ -1,107 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'atomx'; - -function getDomain() { - var domain = ''; - - try { - if ((domain === '') && (window.top == window)) { - domain = window.location.href; - } - - if ((domain === '') && (window.top == window.parent)) { - domain = document.referrer; - } - - if (domain == '') { - var atomxt = 'atomxtest'; - - // It should be impossible to change the window.location.ancestorOrigins. - window.location.ancestorOrigins[0] = atomxt; - if (window.location.ancestorOrigins[0] != atomxt) { - var ancestorOrigins = window.location.ancestorOrigins; - - // If the length is 0 we are a javascript tag running in the main domain. - // But window.top != window or window.location.hostname is empty. - if (ancestorOrigins.length == 0) { - // This browser is so fucked up, just return an empty string. - return ''; - } - - // ancestorOrigins is an array where [0] is our own window.location - // and [length-1] is the top window.location. - domain = ancestorOrigins[ancestorOrigins.length - 1]; - } - } - } catch (unused) { - } - - if (domain === '') { - domain = document.referrer; - } - - if (domain === '') { - domain = window.location.href; - } - - return domain.substr(0, 512); -} - -export const spec = { - code: BIDDER_CODE, - - isBidRequestValid: function(bid) { - return bid.params && (!!bid.params.id); - }, - - buildRequests: function(validBidRequests) { - return validBidRequests.map(bidRequest => { - return { - method: 'GET', - url: 'https://p.ato.mx/placement', - data: { - v: 12, - id: bidRequest.params.id, - size: utils.parseSizesInput(bidRequest.sizes)[0], - prebid: bidRequest.bidId, - b: 0, - h: '7t3y9', - type: 'javascript', - screen: window.screen.width + 'x' + window.screen.height + 'x' + window.screen.colorDepth, - timezone: new Date().getTimezoneOffset(), - domain: getDomain(), - r: document.referrer.substr(0, 512), - }, - }; - }); - }, - - interpretResponse: function (serverResponse, bidRequest) { - const body = serverResponse.body; - const res = { - requestId: body.code, - cpm: body.cpm * 1000, - width: body.width, - height: body.height, - creativeId: body.creative_id, - currency: 'USD', - netRevenue: true, - ttl: 60, - }; - - if (body.adm) { - res.ad = body.adm; - } else { - res.adUrl = body.url; - } - - return [res]; - }, - - getUserSyncs: function(syncOptions, serverResponses) { - return []; - }, -}; -registerBidder(spec); diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index 31e46dead89..f45d2e80055 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -1,7 +1,7 @@ +import { logError, logInfo } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adaptermanager from '../src/adapterManager.js'; -import * as utils from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -13,9 +13,6 @@ export const storage = getStorageManager(); */ const analyticsType = 'endpoint'; -// dev endpoints -// const preflightUrl = 'https://analytics-check.publishersite.xyz/check/'; -// export const analyticsUrl = 'https://analyticsv2.publishersite.xyz'; const preflightUrl = 'https://check.analytics.rlcdn.com/check/'; export const analyticsUrl = 'https://analytics.rlcdn.com'; @@ -23,7 +20,7 @@ export const analyticsUrl = 'https://analytics.rlcdn.com'; let handlerRequest = []; let handlerResponse = []; -let atsAnalyticsAdapterVersion = 1; +let atsAnalyticsAdapterVersion = 3; let browsersList = [ /* Googlebot */ @@ -207,12 +204,6 @@ let browsersList = [ }, ]; -function setSamplingCookie(samplRate) { - let now = new Date(); - now.setTime(now.getTime() + 3600000); - storage.setCookie('_lr_sampling_rate', samplRate, now.toUTCString()); -} - let listOfSupportedBrowsers = ['Safari', 'Chrome', 'Firefox', 'Microsoft Edge']; function bidRequestedHandler(args) { @@ -231,7 +222,8 @@ function bidRequestedHandler(args) { auction_start: new Date(args.auctionStart).toJSON(), domain: window.location.hostname, pid: atsAnalyticsAdapter.context.pid, - adapter_version: atsAnalyticsAdapterVersion + adapter_version: atsAnalyticsAdapterVersion, + bid_won: false }; }); return requests; @@ -256,58 +248,45 @@ export function parseBrowser() { let browserName = result && result.length ? result[0].name : ''; return (listOfSupportedBrowsers.indexOf(browserName) >= 0) ? browserName : 'Unknown'; } catch (err) { - utils.logError('ATS Analytics - Error while checking user browser!', err); + logError('ATS Analytics - Error while checking user browser!', err); } } -function sendDataToAnalytic () { +function sendDataToAnalytic (events) { // send data to ats analytic endpoint try { - let dataToSend = {'Data': atsAnalyticsAdapter.context.events}; + let dataToSend = {'Data': events}; let strJSON = JSON.stringify(dataToSend); - utils.logInfo('ATS Analytics - tried to send analytics data!'); + logInfo('ATS Analytics - tried to send analytics data!'); ajax(analyticsUrl, function () { + logInfo('ATS Analytics - events sent successfully!'); }, strJSON, {method: 'POST', contentType: 'application/json'}); } catch (err) { - utils.logError('ATS Analytics - request encounter an error: ', err); + logError('ATS Analytics - request encounter an error: ', err); } } // preflight request, to check did publisher have permission to send data to analytics endpoint -function preflightRequest (envelopeSourceCookieValue) { - ajax(preflightUrl + atsAnalyticsAdapter.context.pid, function (data) { - let samplingRateObject = JSON.parse(data); - utils.logInfo('ATS Analytics - Sampling Rate: ', samplingRateObject); - let samplingRate = samplingRateObject['samplingRate']; - setSamplingCookie(samplingRate); - let samplingRateNumber = Number(samplingRate); - if (data && samplingRate && atsAnalyticsAdapter.shouldFireRequest(samplingRateNumber) && envelopeSourceCookieValue != null) { - sendDataToAnalytic(); - } - }, undefined, { method: 'GET', crossOrigin: true }); -} - -function callHandler(evtype, args) { - if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) { - handlerRequest = handlerRequest.concat(bidRequestedHandler(args)); - } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) { - handlerResponse.push(bidResponseHandler(args)); - } - if (evtype === CONSTANTS.EVENTS.AUCTION_END) { - if (handlerRequest.length) { - let events = []; - if (handlerResponse.length) { - events = handlerRequest.filter(request => handlerResponse.filter(function(response) { - if (request.bid_id === response.bid_id) { - Object.assign(request, response); - } - })); - } else { - events = handlerRequest; +function preflightRequest (envelopeSourceCookieValue, events) { + logInfo('ATS Analytics - preflight request!'); + ajax(preflightUrl + atsAnalyticsAdapter.context.pid, + { + success: function (data) { + let samplingRateObject = JSON.parse(data); + logInfo('ATS Analytics - Sampling Rate: ', samplingRateObject); + let samplingRate = samplingRateObject.samplingRate; + atsAnalyticsAdapter.setSamplingCookie(samplingRate); + let samplingRateNumber = Number(samplingRate); + if (data && samplingRate && atsAnalyticsAdapter.shouldFireRequest(samplingRateNumber) && envelopeSourceCookieValue != null) { + logInfo('ATS Analytics - events to send: ', events); + sendDataToAnalytic(events); + } + }, + error: function () { + atsAnalyticsAdapter.setSamplingCookie(0); + logInfo('ATS Analytics - Sampling Rate Request Error!'); } - atsAnalyticsAdapter.context.events = events; - } - } + }, undefined, {method: 'GET', crossOrigin: true}); } let atsAnalyticsAdapter = Object.assign(adapter( @@ -317,23 +296,7 @@ let atsAnalyticsAdapter = Object.assign(adapter( { track({eventType, args}) { if (typeof args !== 'undefined') { - callHandler(eventType, args); - } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { - let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats'); - try { - utils.logInfo('ATS Analytics - preflight request!'); - let samplingRateCookie = storage.getCookie('_lr_sampling_rate'); - if (!samplingRateCookie) { - preflightRequest(envelopeSourceCookieValue); - } else { - if (atsAnalyticsAdapter.shouldFireRequest(parseInt(samplingRateCookie)) && envelopeSourceCookieValue != null) { - sendDataToAnalytic(); - } - } - } catch (err) { - utils.logError('ATS Analytics - preflight request encounter an error: ', err); - } + atsAnalyticsAdapter.callHandler(eventType, args); } } }); @@ -341,30 +304,99 @@ let atsAnalyticsAdapter = Object.assign(adapter( // save the base class function atsAnalyticsAdapter.originEnableAnalytics = atsAnalyticsAdapter.enableAnalytics; -// add check to not fire request every time, but instead to send 1/10 events +// add check to not fire request every time, but instead to send 1/100 atsAnalyticsAdapter.shouldFireRequest = function (samplingRate) { - let shouldFireRequestValue = (Math.floor((Math.random() * samplingRate + 1)) === samplingRate); - utils.logInfo('ATS Analytics - Should Fire Request: ', shouldFireRequestValue); - return shouldFireRequestValue; + if (samplingRate !== 0) { + let shouldFireRequestValue = (Math.floor((Math.random() * 100 + 1)) === 100); + logInfo('ATS Analytics - Should Fire Request: ', shouldFireRequestValue); + return shouldFireRequestValue; + } else { + logInfo('ATS Analytics - Should Fire Request: ', false); + return false; + } }; atsAnalyticsAdapter.getUserAgent = function () { return window.navigator.userAgent; }; + +atsAnalyticsAdapter.setSamplingCookie = function (samplRate) { + const now = new Date(); + now.setTime(now.getTime() + 86400000); + storage.setCookie('_lr_sampling_rate', samplRate, now.toUTCString()); +} + // override enableAnalytics so we can get access to the config passed in from the page atsAnalyticsAdapter.enableAnalytics = function (config) { if (!config.options.pid) { - utils.logError('ATS Analytics - Publisher ID (pid) option is not defined. Analytics won\'t work'); + logError('ATS Analytics - Publisher ID (pid) option is not defined. Analytics won\'t work'); return; } atsAnalyticsAdapter.context = { events: [], - pid: config.options.pid + pid: config.options.pid, + bidWonTimeout: config.options.bidWonTimeout }; let initOptions = config.options; + logInfo('ATS Analytics - adapter enabled! '); atsAnalyticsAdapter.originEnableAnalytics(initOptions); // call the base class function }; +atsAnalyticsAdapter.callHandler = function (evtype, args) { + if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) { + handlerRequest = handlerRequest.concat(bidRequestedHandler(args)); + } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) { + handlerResponse.push(bidResponseHandler(args)); + } + if (evtype === CONSTANTS.EVENTS.AUCTION_END) { + let bidWonTimeout = atsAnalyticsAdapter.context.bidWonTimeout ? atsAnalyticsAdapter.context.bidWonTimeout : 2000; + let events = []; + setTimeout(() => { + let winningBids = $$PREBID_GLOBAL$$.getAllWinningBids(); + logInfo('ATS Analytics - winning bids: ', winningBids) + // prepare format data for sending to analytics endpoint + if (handlerRequest.length) { + let wonEvent = {}; + if (handlerResponse.length) { + events = handlerRequest.filter(request => handlerResponse.filter(function (response) { + if (request.bid_id === response.bid_id) { + Object.assign(request, response); + } + })); + if (winningBids.length) { + events = events.filter(event => winningBids.filter(function (won) { + wonEvent.bid_id = won.requestId; + wonEvent.bid_won = true; + if (event.bid_id === wonEvent.bid_id) { + Object.assign(event, wonEvent); + } + })) + } + } else { + events = handlerRequest; + } + // check should we send data to analytics or not, check first cookie value _lr_sampling_rate + try { + let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats'); + let samplingRateCookie = storage.getCookie('_lr_sampling_rate'); + if (!samplingRateCookie) { + preflightRequest(envelopeSourceCookieValue, events); + } else { + if (atsAnalyticsAdapter.shouldFireRequest(parseInt(samplingRateCookie)) && envelopeSourceCookieValue != null) { + logInfo('ATS Analytics - events to send: ', events); + sendDataToAnalytic(events); + } + } + // empty events array to not send duplicate events + events = []; + } catch (err) { + logError('ATS Analytics - preflight request encounter an error: ', err); + } + } + }, bidWonTimeout); + } +} + adaptermanager.registerAnalyticsAdapter({ adapter: atsAnalyticsAdapter, code: 'atsAnalytics', diff --git a/modules/atsAnalyticsAdapter.md b/modules/atsAnalyticsAdapter.md index 7c634f39ae2..17819ac61b3 100644 --- a/modules/atsAnalyticsAdapter.md +++ b/modules/atsAnalyticsAdapter.md @@ -17,6 +17,7 @@ Analytics adapter for Authenticated Traffic Solution(ATS), provided by LiveRamp. provider: 'atsAnalytics', options: { pid: '999', // publisher ID + bidWonTimeout: 2000 // on auction end for how long to wait for bid_won events, by default it's 2000 miliseconds, if it's not set it will be 2000 miliseconds. } } ``` diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js index b90471ee21a..2c100bce27b 100644 --- a/modules/audiencerunBidAdapter.js +++ b/modules/audiencerunBidAdapter.js @@ -1,13 +1,55 @@ -import * as utils from '../src/utils.js'; +import { deepAccess, isFn, logError, getValue, getBidIdParameter, _each, isArray, triggerPixel } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'audiencerun'; -const ENDPOINT_URL = 'https://d.audiencerun.com/prebid'; +const BASE_URL = 'https://d.audiencerun.com'; +const AUCTION_URL = `${BASE_URL}/prebid`; +const TIMEOUT_EVENT_URL = `${BASE_URL}/ps/pbtimeout`; +const DEFAULT_CURRENCY = 'USD'; + +let requestedBids = []; + +/** + * Gets bidder request referer + * + * @param {Object} bidderRequest + * @return {string} + */ +function getPageUrl(bidderRequest) { + return ( + config.getConfig('pageUrl') || + deepAccess(bidderRequest, 'refererInfo.referer') || + null + ); +} + +/** + * Returns bidfloor through floors module if available + * + * @param {Object} bid + * @returns {number} + */ +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: BANNER, + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} export const spec = { - version: '1.0.0', + version: '1.1.0', code: BIDDER_CODE, supportedMediaTypes: [BANNER], @@ -19,8 +61,8 @@ export const spec = { */ isBidRequestValid: function (bid) { let isValid = true; - if (!utils.deepAccess(bid, 'params.zoneId')) { - utils.logError('AudienceRun zoneId parameter is required. Bid aborted.'); + if (!deepAccess(bid, 'params.zoneId')) { + logError('AudienceRun zoneId parameter is required. Bid aborted.'); isValid = false; } return isValid; @@ -33,50 +75,53 @@ export const spec = { * @param {*} bidderRequest * @return {ServerRequest} Info describing the request to the server. */ - buildRequests: function(bidRequests, bidderRequest) { - const bids = bidRequests.map(bid => { - const sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes', []); + buildRequests: function (bidRequests, bidderRequest) { + const bids = bidRequests.map((bid) => { + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes', []); return { - zoneId: utils.getValue(bid.params, 'zoneId'), - sizes: sizes.map(size => ({ + zoneId: getValue(bid.params, 'zoneId'), + sizes: sizes.map((size) => ({ w: size[0], - h: size[1] + h: size[1], })), - bidfloor: bid.params.bidfloor || 0.0, + bidfloor: getBidFloor(bid), bidId: bid.bidId, - bidderRequestId: utils.getBidIdParameter('bidderRequestId', bid), - adUnitCode: utils.getBidIdParameter('adUnitCode', bid), - auctionId: utils.getBidIdParameter('auctionId', bid), - transactionId: utils.getBidIdParameter('transactionId', bid) + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + adUnitCode: getBidIdParameter('adUnitCode', bid), + auctionId: getBidIdParameter('auctionId', bid), + transactionId: getBidIdParameter('transactionId', bid), }; }); const payload = { libVersion: this.version, - referer: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null, + referer: getPageUrl(bidderRequest), currencyCode: config.getConfig('currency.adServerCurrency'), timeout: config.getConfig('bidderTimeout'), - bids + bids, }; if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr = { consent: bidderRequest.gdprConsent.consentString, - applies: bidderRequest.gdprConsent.gdprApplies + applies: bidderRequest.gdprConsent.gdprApplies, + version: bidderRequest.gdprConsent.apiVersion, }; } else { payload.gdpr = { - consent: '' - } + consent: '', + }; } + requestedBids = bids; + return { method: 'POST', - url: ENDPOINT_URL, + url: AUCTION_URL, data: JSON.stringify(payload), options: { - withCredentials: true - } + withCredentials: true, + }, }; }, @@ -88,7 +133,7 @@ export const spec = { */ interpretResponse: function (serverResponse, bidRequest) { const bids = []; - utils._each(serverResponse.body.bid, function (bidObject) { + _each(serverResponse.body.bid, function (bidObject) { if (!bidObject.cpm || bidObject.cpm === null || !bidObject.adm) { return; } @@ -100,15 +145,22 @@ export const spec = { // Common properties bid.requestId = bidObject.bidId; - bid.adId = bidObject.zoneId; bid.cpm = parseFloat(bidObject.cpm); bid.creativeId = bidObject.crid; - bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; + bid.currency = bidObject.currency + ? bidObject.currency.toUpperCase() + : DEFAULT_CURRENCY; bid.height = bidObject.h; bid.width = bidObject.w; bid.netRevenue = bidObject.isNet ? bidObject.isNet : false; bid.ttl = 300; + bid.meta = { + advertiserDomains: + bidObject.adomain && Array.isArray(bidObject.adomain) + ? bidObject.adomain + : [], + }; bids.push(bid); }); @@ -122,21 +174,42 @@ 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) { if (!serverResponses || !serverResponses.length) return []; const syncs = []; - serverResponses.forEach(response => { - response.body.bid.forEach(bidObject => { + serverResponses.forEach((response) => { + response.body.bid.forEach((bidObject) => { syncs.push({ type: 'iframe', - url: bidObject.syncUrl + url: bidObject.syncUrl, }); }); }); return syncs; - } + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * + * @param {Array} timeoutData timeout specific data + */ + onTimeout: function (timeoutData) { + if (!isArray(timeoutData)) { + return; + } + + timeoutData.forEach((bid) => { + const bidOnTimeout = requestedBids.find((requestedBid) => requestedBid.bidId === bid.bidId); + + if (bidOnTimeout) { + triggerPixel( + `${TIMEOUT_EVENT_URL}/${bidOnTimeout.zoneId}/${bidOnTimeout.bidId}` + ); + } + }); + }, }; registerBidder(spec); diff --git a/modules/audiencerunBidAdapter.md b/modules/audiencerunBidAdapter.md index 3704922fdd5..2257e939f3b 100644 --- a/modules/audiencerunBidAdapter.md +++ b/modules/audiencerunBidAdapter.md @@ -2,7 +2,7 @@ **Module Name**: AudienceRun Bidder Adapter **Module Type**: Bidder Adapter -**Maintainer**: prebid@audiencerun.com +**Maintainer**: github@audiencerun.com # Description diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js index 415c52ba6d3..726bbef9bd6 100644 --- a/modules/automatadBidAdapter.js +++ b/modules/automatadBidAdapter.js @@ -1,11 +1,11 @@ +import { logInfo } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js' -import * as utils from '../src/utils.js' import {BANNER} from '../src/mediaTypes.js' import {ajax} from '../src/ajax.js' const BIDDER = 'automatad' -const ENDPOINT_URL = 'https://rtb2.automatad.com/ortb2' +const ENDPOINT_URL = 'https://bid.atmtd.com' const DEFAULT_BID_TTL = 30 const DEFAULT_CURRENCY = 'USD' @@ -57,7 +57,7 @@ export const spec = { const payloadString = JSON.stringify(openrtbRequest) return { method: 'POST', - url: ENDPOINT_URL + '/resp', + url: ENDPOINT_URL + '/request', data: payloadString, options: { contentType: 'application/json', @@ -72,6 +72,7 @@ export const spec = { const response = (serverResponse || {}).body if (response && response.seatbid && response.seatbid[0].bid && response.seatbid[0].bid.length) { + var bidid = response.bidid response.seatbid.forEach(bidObj => { bidObj.bid.forEach(bid => { bidResponses.push({ @@ -88,20 +89,19 @@ export const spec = { height: bid.h, netRevenue: DEFAULT_NET_REVENUE, nurl: bid.nurl, + bidId: bidid }) }) }) } else { - utils.logInfo('automatad :: no valid responses to interpret') + logInfo('automatad :: no valid responses to interpret') } return bidResponses }, - getUserSyncs: function(syncOptions, serverResponse) { - return [{ - type: 'iframe', - url: 'https://rtb2.automatad.com/ortb2/async_usersync' - }] + onTimeout: function(timeoutData) { + const timeoutUrl = ENDPOINT_URL + '/timeout' + ajax(timeoutUrl, null, JSON.stringify(timeoutData)) }, onBidWon: function(bid) { if (!bid.nurl) { return } @@ -116,6 +116,9 @@ export const spec = { ).replace( /\$\{AUCTION_CURRENCY\}/, winCurr + ).replace( + /\$\{AUCTON_BID_ID\}/, + bid.bidId ).replace( /\$\{AUCTION_ID\}/, bid.auctionId diff --git a/modules/avocetBidAdapter.js b/modules/avocetBidAdapter.js deleted file mode 100644 index 1283bb865d4..00000000000 --- a/modules/avocetBidAdapter.js +++ /dev/null @@ -1,141 +0,0 @@ -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'avct'; -const DEFAULT_BASE_URL = 'https://ads.avct.cloud'; -const DEFAULT_PREBID_PATH = '/prebid'; - -function getPrebidURL() { - let host = config.getConfig('avct.baseUrl'); - if (host && typeof host === 'string') { - return `${host}${getPrebidPath()}`; - } - return `${DEFAULT_BASE_URL}${getPrebidPath()}`; -} - -function getPrebidPath() { - let prebidPath = config.getConfig('avct.prebidPath'); - if (prebidPath && typeof prebidPath === 'string') { - return prebidPath; - } - return DEFAULT_PREBID_PATH; -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid with params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function (bid) { - return ( - !!bid.params && - !!bid.params.placement && - typeof bid.params.placement === 'string' && - bid.params.placement.length === 24 - ); - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function (bidRequests, bidderRequest) { - // Get currency from config - const currency = config.getConfig('currency.adServerCurrency'); - - // Publisher domain from config - const publisherDomain = config.getConfig('publisherDomain'); - - // First-party data from config - const fpd = config.getLegacyFpd(config.getConfig('ortb2')); - - // GDPR status and TCF consent string - let tcfConsentString; - let gdprApplies = false; - if (bidderRequest.gdprConsent) { - tcfConsentString = bidderRequest.gdprConsent.consentString; - gdprApplies = !!bidderRequest.gdprConsent.gdprApplies; - } - - // US privacy string - let usPrivacyString; - if (bidderRequest.uspConsent) { - usPrivacyString = bidderRequest.uspConsent; - } - - // Supply chain - let schain; - if (bidderRequest.schain) { - schain = bidderRequest.schain; - } - - // ID5 identifier - let id5id; - if (bidRequests[0].userId && bidRequests[0].userId.id5id && bidRequests[0].userId.id5id.uid) { - id5id = bidRequests[0].userId.id5id.uid; - } - - // Build the avocet ext object - const ext = { - currency, - tcfConsentString, - gdprApplies, - usPrivacyString, - schain, - publisherDomain, - fpd, - id5id, - }; - - // Extract properties from bidderRequest - const { - auctionId, - auctionStart, - bidderCode, - bidderRequestId, - refererInfo, - timeout, - } = bidderRequest; - - // Construct payload - const payload = JSON.stringify({ - auctionId, - auctionStart, - bidderCode, - bidderRequestId, - refererInfo, - timeout, - bids: bidRequests, - ext, - }); - - return { - method: 'POST', - url: getPrebidURL(), - data: payload, - }; - }, - interpretResponse: function (serverResponse, bidRequest) { - if ( - !serverResponse || - !serverResponse.body || - typeof serverResponse.body !== 'object' - ) { - return []; - } - if (Array.isArray(serverResponse.body)) { - return serverResponse.body; - } - if (Array.isArray(serverResponse.body.responses)) { - return serverResponse.body.responses; - } - return []; - }, -}; -registerBidder(spec); diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js index daaac27e6a4..7cd8f63bd2a 100644 --- a/modules/axonixBidAdapter.js +++ b/modules/axonixBidAdapter.js @@ -1,7 +1,7 @@ +import { isArray, logError, deepAccess, isEmpty, triggerPixel, replaceAuctionPrice } 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 * as utils from '../src/utils.js'; import { ajax } from '../src/ajax.js'; const BIDDER_CODE = 'axonix'; @@ -68,9 +68,9 @@ export const spec = { // video bid request validation if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { if (!bid.mediaTypes[VIDEO].hasOwnProperty('mimes') || - !utils.isArray(bid.mediaTypes[VIDEO].mimes) || + !isArray(bid.mediaTypes[VIDEO].mimes) || bid.mediaTypes[VIDEO].mimes.length === 0) { - utils.logError('mimes are mandatory for video bid request. Ad Unit: ', JSON.stringify(bid)); + logError('mimes are mandatory for video bid request. Ad Unit: ', JSON.stringify(bid)); return false; } @@ -142,7 +142,7 @@ export const spec = { interpretResponse: function(serverResponse) { const response = serverResponse ? serverResponse.body : []; - if (!utils.isArray(response)) { + if (!isArray(response)) { return []; } @@ -160,9 +160,9 @@ export const spec = { }, onTimeout: function(timeoutData) { - const params = utils.deepAccess(timeoutData, '0.params.0'); + const params = deepAccess(timeoutData, '0.params.0'); - if (!utils.isEmpty(params)) { + if (!isEmpty(params)) { ajax(getURL(params, 'prebid/timeout'), null, timeoutData[0], { method: 'POST', options: { @@ -177,7 +177,7 @@ export const spec = { const { nurl } = bid || {}; if (bid.nurl) { - utils.triggerPixel(utils.replaceAuctionPrice(nurl, bid.cpm)); + triggerPixel(replaceAuctionPrice(nurl, bid.cpm)); }; } } diff --git a/modules/axonixBidAdapter.md b/modules/axonixBidAdapter.md index 7a4606d5502..acbaae1d4b0 100644 --- a/modules/axonixBidAdapter.md +++ b/modules/axonixBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name : Axonix Bidder Adapter Module Type : Bidder Adapter -Maintainer : support+prebid@axonix.com +Maintainer : support.axonix@emodoinc.com ``` # Description diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 8ddc0ca5ba9..e705156d4a2 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { logWarn, deepAccess, deepSetValue, deepClone, isArray, parseSizesInput, isFn, parseUrl, getUniqueIdentifierStr } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; @@ -6,9 +6,10 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js'; import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; -const ADAPTER_VERSION = '1.15'; +const ADAPTER_VERSION = '1.19'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; +const CURRENCY = 'USD'; export const VIDEO_ENDPOINT = 'https://reachms.bfmio.com/bid.json?exchange_id='; export const BANNER_ENDPOINT = 'https://display.bfmio.com/prebid_display'; @@ -19,7 +20,9 @@ export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; export const SUPPORTED_USER_IDS = [ { key: 'tdid', source: 'adserver.org', rtiPartner: 'TDID', queryParam: 'tdid' }, - { key: 'idl_env', source: 'liveramp.com', rtiPartner: 'idl', queryParam: 'idl' } + { key: 'idl_env', source: 'liveramp.com', rtiPartner: 'idl', queryParam: 'idl' }, + { key: 'uid2.id', source: 'uidapi.com', rtiPartner: 'UID2', queryParam: 'uid2' }, + { key: 'haloId', source: 'audigent.com', atype: 1, queryParam: 'haloid' } ]; let appId = ''; @@ -29,7 +32,27 @@ export const spec = { supportedMediaTypes: [ VIDEO, BANNER ], isBidRequestValid(bid) { - return !!(isVideoBidValid(bid) || isBannerBidValid(bid)); + if (isVideoBid(bid)) { + if (!getVideoBidParam(bid, 'appId')) { + logWarn('Beachfront: appId param is required for video bids.'); + return false; + } + if (!getVideoBidParam(bid, 'bidfloor')) { + logWarn('Beachfront: bidfloor param is required for video bids.'); + return false; + } + } + if (isBannerBid(bid)) { + if (!getBannerBidParam(bid, 'appId')) { + logWarn('Beachfront: appId param is required for banner bids.'); + return false; + } + if (!getBannerBidParam(bid, 'bidfloor')) { + logWarn('Beachfront: bidfloor param is required for banner bids.'); + return false; + } + } + return true; }, buildRequests(bids, bidderRequest) { @@ -62,13 +85,14 @@ export const spec = { if (isVideoBid(bidRequest)) { if (!response || !response.bidPrice) { - utils.logWarn(`No valid video bids from ${spec.code} bidder`); + logWarn(`No valid video bids from ${spec.code} bidder`); return []; } let sizes = getVideoSizes(bidRequest); let firstSize = getFirstSize(sizes); - let context = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); + let context = deepAccess(bidRequest, 'mediaTypes.video.context'); let responseType = getVideoBidParam(bidRequest, 'responseType') || 'both'; + let responseMeta = Object.assign({ mediaType: VIDEO, advertiserDomains: [] }, response.meta); let bidResponse = { requestId: bidRequest.bidId, bidderCode: spec.code, @@ -76,9 +100,10 @@ export const spec = { width: firstSize.w, height: firstSize.h, creativeId: response.crid || response.cmpId, + meta: responseMeta, renderer: context === OUTSTREAM ? createRenderer(bidRequest) : null, mediaType: VIDEO, - currency: 'USD', + currency: CURRENCY, netRevenue: true, ttl: 300 }; @@ -94,13 +119,14 @@ export const spec = { return bidResponse; } else { if (!response || !response.length) { - utils.logWarn(`No valid banner bids from ${spec.code} bidder`); + logWarn(`No valid banner bids from ${spec.code} bidder`); return []; } return response .filter(bid => bid.adm) .map((bid) => { let request = find(bidRequest, req => req.adUnitCode === bid.slot); + let responseMeta = Object.assign({ mediaType: BANNER, advertiserDomains: [] }, bid.meta); return { requestId: request.bidId, bidderCode: spec.code, @@ -109,8 +135,9 @@ export const spec = { cpm: bid.price, width: bid.w, height: bid.h, + meta: responseMeta, mediaType: BANNER, - currency: 'USD', + currency: CURRENCY, netRevenue: true, ttl: 300 }; @@ -121,7 +148,7 @@ export const spec = { getUserSyncs(syncOptions, serverResponses = [], gdprConsent = {}, uspConsent = '') { let syncs = []; let { gdprApplies, consentString = '' } = gdprConsent; - let bannerResponse = find(serverResponses, (res) => utils.isArray(res.body)); + let bannerResponse = find(serverResponses, (res) => isArray(res.body)); if (bannerResponse) { if (syncOptions.iframeEnabled) { @@ -179,7 +206,7 @@ function getFirstSize(sizes) { } function parseSizes(sizes) { - return utils.parseSizesInput(sizes).map(size => { + return parseSizesInput(sizes).map(size => { let [ width, height ] = size.split('x'); return { w: parseInt(width, 10) || undefined, @@ -189,11 +216,11 @@ function parseSizes(sizes) { } function getVideoSizes(bid) { - return parseSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); + return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); } function getBannerSizes(bid) { - return parseSizes(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); + return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); } function getOsVersion() { @@ -230,26 +257,36 @@ function getDoNotTrack() { } function isVideoBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.video'); + return deepAccess(bid, 'mediaTypes.video'); } function isBannerBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); + return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); } function getVideoBidParam(bid, key) { - return utils.deepAccess(bid, 'params.video.' + key) || utils.deepAccess(bid, 'params.' + key); + return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); } function getBannerBidParam(bid, key) { - return utils.deepAccess(bid, 'params.banner.' + key) || utils.deepAccess(bid, 'params.' + key); + return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key); } function getPlayerBidParam(bid, key, defaultValue) { - let param = utils.deepAccess(bid, 'params.player.' + key); + let param = deepAccess(bid, 'params.player.' + key); return param === undefined ? defaultValue : param; } +function getBannerBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: CURRENCY, mediaType: 'banner', size: '*' }) : {}; + return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); +} + +function getVideoBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: CURRENCY, mediaType: 'video', size: '*' }) : {}; + return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); +} + function isVideoBidValid(bid) { return isVideoBid(bid) && getVideoBidParam(bid, 'appId') && getVideoBidParam(bid, 'bidfloor'); } @@ -260,7 +297,7 @@ function isBannerBidValid(bid) { function getTopWindowLocation(bidderRequest) { let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; - return utils.parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); + return parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); } function getTopWindowReferrer() { @@ -278,19 +315,23 @@ function getEids(bid) { } function getUserId(bid) { - return ({ key, source, rtiPartner }) => { - let id = bid.userId && bid.userId[key]; - return id ? formatEid(id, source, rtiPartner) : null; + return ({ key, source, rtiPartner, atype }) => { + let id = deepAccess(bid, `userId.${key}`); + return id ? formatEid(id, source, rtiPartner, atype) : null; }; } -function formatEid(id, source, rtiPartner) { +function formatEid(id, source, rtiPartner, atype) { + let uid = { id }; + if (rtiPartner) { + uid.ext = { rtiPartner }; + } + if (atype) { + uid.atype = atype; + } return { source, - uids: [{ - id, - ext: { rtiPartner } - }] + uids: [uid] }; } @@ -315,15 +356,16 @@ function createVideoRequestData(bid, bidderRequest) { let firstSize = getFirstSize(sizes); let video = getVideoTargetingParams(bid); let appId = getVideoBidParam(bid, 'appId'); - let bidfloor = getVideoBidParam(bid, 'bidfloor'); + let bidfloor = getVideoBidFloor(bid); let tagid = getVideoBidParam(bid, 'tagid'); let topLocation = getTopWindowLocation(bidderRequest); let eids = getEids(bid); + let ortb2 = deepClone(config.getConfig('ortb2')); let payload = { isPrebid: true, appId: appId, domain: document.location.hostname, - id: utils.getUniqueIdentifierStr(), + id: getUniqueIdentifierStr(), imp: [{ video: Object.assign({ w: firstSize.w, @@ -337,6 +379,7 @@ function createVideoRequestData(bid, bidderRequest) { displaymanagerver: ADAPTER_VERSION }], site: { + ...deepAccess(ortb2, 'site', {}), page: topLocation.href, domain: topLocation.hostname }, @@ -348,39 +391,32 @@ function createVideoRequestData(bid, bidderRequest) { js: 1, geo: {} }, - regs: { - ext: {} - }, - source: { - ext: {} - }, - user: { - ext: {} - }, - cur: ['USD'] + app: deepAccess(ortb2, 'app'), + user: deepAccess(ortb2, 'user'), + cur: [CURRENCY] }; if (bidderRequest && bidderRequest.uspConsent) { - payload.regs.ext.us_privacy = bidderRequest.uspConsent; + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } if (bidderRequest && bidderRequest.gdprConsent) { let { gdprApplies, consentString } = bidderRequest.gdprConsent; - payload.regs.ext.gdpr = gdprApplies ? 1 : 0; - payload.user.ext.consent = consentString; + deepSetValue(payload, 'regs.ext.gdpr', gdprApplies ? 1 : 0); + deepSetValue(payload, 'user.ext.consent', consentString); } if (bid.schain) { - payload.source.ext.schain = bid.schain; + deepSetValue(payload, 'source.ext.schain', bid.schain); } if (eids.length > 0) { - payload.user.ext.eids = eids; + deepSetValue(payload, 'user.ext.eids', eids); } let connection = navigator.connection || navigator.webkitConnection; if (connection && connection.effectiveType) { - payload.device.connectiontype = connection.effectiveType; + deepSetValue(payload, 'device.connectiontype', connection.effectiveType); } return payload; @@ -393,13 +429,15 @@ function createBannerRequestData(bids, bidderRequest) { return { slot: bid.adUnitCode, id: getBannerBidParam(bid, 'appId'), - bidfloor: getBannerBidParam(bid, 'bidfloor'), + bidfloor: getBannerBidFloor(bid), tagid: getBannerBidParam(bid, 'tagid'), sizes: getBannerSizes(bid) }; }); + let ortb2 = deepClone(config.getConfig('ortb2')); let payload = { slots: slots, + ortb2: ortb2, page: topLocation.href, domain: topLocation.hostname, search: topLocation.search, @@ -428,7 +466,7 @@ function createBannerRequestData(bids, bidderRequest) { } SUPPORTED_USER_IDS.forEach(({ key, queryParam }) => { - let id = bids[0] && bids[0].userId && bids[0].userId[key]; + let id = deepAccess(bids, `0.userId.${key}`) if (id) { payload[queryParam] = id; } diff --git a/modules/beachfrontBidAdapter.md b/modules/beachfrontBidAdapter.md index a2eb79ee331..9de415f8fc5 100644 --- a/modules/beachfrontBidAdapter.md +++ b/modules/beachfrontBidAdapter.md @@ -18,7 +18,8 @@ Module that connects to Beachfront's demand sources mediaTypes: { video: { context: 'instream', - playerSize: [ 640, 360 ] + playerSize: [640, 360], + mimes: ['video/mp4', 'application/javascript'] } }, bids: [ @@ -26,10 +27,7 @@ Module that connects to Beachfront's demand sources bidder: 'beachfront', params: { bidfloor: 0.01, - appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', - video: { - mimes: [ 'video/mp4', 'application/javascript' ] - } + appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' } } ] @@ -37,7 +35,7 @@ Module that connects to Beachfront's demand sources code: 'test-banner', mediaTypes: { banner: { - sizes: [ 300, 250 ] + sizes: [300, 250] } }, bids: [ @@ -61,10 +59,11 @@ Module that connects to Beachfront's demand sources mediaTypes: { video: { context: 'outstream', - playerSize: [ 640, 360 ] + playerSize: [640, 360], + mimes: ['video/mp4', 'application/javascript'] }, banner: { - sizes: [ 300, 250 ] + sizes: [300, 250] } }, bids: [ @@ -74,7 +73,6 @@ Module that connects to Beachfront's demand sources video: { bidfloor: 0.01, appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', - mimes: [ 'video/mp4', 'application/javascript' ] }, banner: { bidfloor: 0.01, @@ -95,7 +93,8 @@ Module that connects to Beachfront's demand sources mediaTypes: { video: { context: 'outstream', - playerSize: [ 640, 360 ] + playerSize: [ 640, 360 ], + mimes: ['video/mp4', 'application/javascript'] } }, bids: [ @@ -104,8 +103,7 @@ Module that connects to Beachfront's demand sources params: { video: { bidfloor: 0.01, - appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', - mimes: [ 'video/mp4', 'application/javascript' ] + appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' }, player: { progressColor: '#50A8FA', diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js new file mode 100644 index 00000000000..2e74170fcaf --- /dev/null +++ b/modules/beopBidAdapter.js @@ -0,0 +1,144 @@ +import { deepAccess, isArray, logWarn, triggerPixel, buildUrl, logInfo, getValue, getBidIdParameter } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +const BIDDER_CODE = 'beop'; +const ENDPOINT_URL = 'https://hb.beop.io/bid'; +const TCF_VENDOR_ID = 666; + +const validIdRegExp = /^[0-9a-fA-F]{24}$/ + +export const spec = { + code: BIDDER_CODE, + gvlid: TCF_VENDOR_ID, + aliases: ['bp'], + /** + * Test if the bid request is valid. + * + * @param {bid} : The Bid params + * @return boolean true if the bid request is valid (aka contains a valid accountId or networkId and is open for BANNER), false otherwise. + */ + isBidRequestValid: function(bid) { + const id = bid.params.accountId || bid.params.networkId; + if (id === null || typeof id === 'undefined') { + return false + } + if (!validIdRegExp.test(id)) { + return false + } + return bid.mediaTypes.banner !== null && typeof bid.mediaTypes.banner !== 'undefined'; + }, + /** + * Create a BeOp server request from a list of BidRequest + * + * @param {validBidRequests[], ...} : The array of validated bidRequests + * @param {... , bidderRequest} : Common params for each bidRequests + * @return ServerRequest Info describing the request to the BeOp's server + */ + buildRequests: function(validBidRequests, bidderRequest) { + const slots = validBidRequests.map(beOpRequestSlotsMaker); + let pageUrl = deepAccess(window, 'location.href') || deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl'); + let fpd = config.getLegacyFpd(config.getConfig('ortb2')); + let gdpr = bidderRequest.gdprConsent; + let firstSlot = slots[0]; + let payloadObject = { + at: new Date().toString(), + nid: firstSlot.nid, + nptnid: firstSlot.nptnid, + pid: firstSlot.pid, + url: pageUrl, + lang: (window.navigator.language || window.navigator.languages[0]), + kwds: (fpd && fpd.site && fpd.site.keywords) || [], + dbg: false, + slts: slots, + is_amp: deepAccess(bidderRequest, 'referrerInfo.isAmp'), + tc_string: (gdpr && gdpr.gdprApplies) ? gdpr.consentString : null, + }; + const payloadString = JSON.stringify(payloadObject); + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadString + } + }, + interpretResponse: function(serverResponse, request) { + if (serverResponse && serverResponse.body && isArray(serverResponse.body.bids) && serverResponse.body.bids.length > 0) { + return serverResponse.body.bids; + } + return []; + }, + onTimeout: function(timeoutData) { + if (timeoutData === null || typeof timeoutData === 'undefined' || Object.keys(timeoutData).length === 0) { + return; + } + + let trackingParams = buildTrackingParams(timeoutData, 'timeout', timeoutData.timeout); + + logWarn(BIDDER_CODE + ': timed out request'); + triggerPixel(buildUrl({ + protocol: 'https', + hostname: 't.beop.io', + pathname: '/bid', + search: trackingParams + })); + }, + onBidWon: function(bid) { + if (bid === null || typeof bid === 'undefined' || Object.keys(bid).length === 0) { + return; + } + let trackingParams = buildTrackingParams(bid, 'won', bid.cpm); + + logInfo(BIDDER_CODE + ': won request'); + triggerPixel(buildUrl({ + protocol: 'https', + hostname: 't.beop.io', + pathname: '/bid', + search: trackingParams + })); + }, + onSetTargeting: function(bid) {} +} + +function buildTrackingParams(data, info, value) { + const accountId = data.params.accountId; + return { + pid: accountId === undefined ? data.ad.match(/account: \“([a-f\d]{24})\“/)[1] : accountId, + nid: data.params.networkId, + nptnid: data.params.networkPartnerId, + bid: data.bidId || data.requestId, + sl_n: data.adUnitCode, + aid: data.auctionId, + se_ca: 'bid', + se_ac: info, + se_va: value, + url: window.location.href + }; +} + +function beOpRequestSlotsMaker(bid) { + const bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); + const publisherCurrency = config.getConfig('currency.adServerCurrency') || getValue(bid.params, 'currency') || 'EUR'; + let floor; + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({currency: publisherCurrency, mediaType: 'banner', size: [1, 1]}); + if (typeof floorInfo === 'object' && floorInfo.currency === publisherCurrency && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } + return { + sizes: isArray(bannerSizes) ? bannerSizes : bid.sizes, + flr: floor, + pid: getValue(bid.params, 'accountId'), + nid: getValue(bid.params, 'networkId'), + nptnid: getValue(bid.params, 'networkPartnerId'), + bid: getBidIdParameter('bidId', bid), + brid: getBidIdParameter('bidderRequestId', bid), + name: getBidIdParameter('adUnitCode', bid), + aid: getBidIdParameter('auctionId', bid), + tid: getBidIdParameter('transactionId', bid), + brc: getBidIdParameter('bidRequestsCount', bid), + bdrc: getBidIdParameter('bidderRequestCount', bid), + bwc: getBidIdParameter('bidderWinsCount', bid), + } +} + +registerBidder(spec); diff --git a/modules/beopBidAdapter.md b/modules/beopBidAdapter.md new file mode 100644 index 00000000000..c0e88cb1ceb --- /dev/null +++ b/modules/beopBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +**Module Name** : BeOp Bidder Adapter +**Module Type** : Bidder Adapter +**Maintainer** : tech@beop.io + +# Description + +Module that connects to BeOp's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'in-article', + mediaTypes: { + banner: { + sizes: [[1,1]], + } + }, + bids: [ + { + bidder: "beop", + params: { + accountId: '5a8af500c9e77c00017e4cad', + currency: 'EUR' + } + } + ] + } + ]; +``` + diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index f6360332368..b2f63488e12 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -1,14 +1,16 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { getAdUnitSizes, parseSizesInput, deepAccess } from '../src/utils.js'; +import { getAdUnitSizes, parseSizesInput } from '../src/utils.js'; import { getRefererInfo } from '../src/refererDetection.js'; const BIDDER_CODE = 'between'; -const ENDPOINT = 'https://ads.betweendigital.com/adjson?t=prebid'; +let ENDPOINT = 'https://ads.betweendigital.com/adjson?t=prebid'; +const CODE_TYPES = ['inpage', 'preroll', 'midroll', 'postroll']; +const includes = require('core-js-pure/features/array/includes.js'); export const spec = { code: BIDDER_CODE, aliases: ['btw'], - supportedMediaTypes: ['banner'], + supportedMediaTypes: ['banner', 'video'], /** * Determines whether or not the given bid request is valid. * @@ -21,7 +23,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { @@ -29,21 +31,32 @@ export const spec = { const gdprConsent = bidderRequest && bidderRequest.gdprConsent; const refInfo = getRefererInfo(); - validBidRequests.forEach(i => { + validBidRequests.forEach((i) => { + const video = i.mediaTypes && i.mediaTypes.video; + let params = { + eids: getUsersIds(i), sizes: parseSizesInput(getAdUnitSizes(i)), jst: 'hb', ord: Math.random() * 10000000000000000, tz: getTz(), fl: getFl(), rr: getRr(), - shid: getSharedId(i)('id'), - shid3: getSharedId(i)('third'), - s: i.params.s, + s: i.params && i.params.s, bidid: i.bidId, transactionid: i.transactionId, auctionid: i.auctionId }; + + if (video) { + params.mediaType = 2; + params.maxd = video.maxd; + params.mind = video.mind; + params.pos = 'atf'; + ENDPOINT += '&jst=pvc'; + params.codeType = includes(CODE_TYPES, video.codeType) ? video.codeType : 'inpage'; + } + if (i.params.itu !== undefined) { params.itu = i.params.itu; } @@ -94,18 +107,25 @@ export const spec = { */ interpretResponse: function(serverResponse, bidRequest) { const bidResponses = []; + for (var i = 0; i < serverResponse.body.length; i++) { let bidResponse = { requestId: serverResponse.body[i].bidid, cpm: serverResponse.body[i].cpm || 0, width: serverResponse.body[i].w, height: serverResponse.body[i].h, + vastXml: serverResponse.body[i].vastXml, + mediaType: serverResponse.body[i].mediaType, ttl: serverResponse.body[i].ttl, creativeId: serverResponse.body[i].creativeid, currency: serverResponse.body[i].currency || 'RUB', netRevenue: serverResponse.body[i].netRevenue || true, - ad: serverResponse.body[i].ad + ad: serverResponse.body[i].ad, + meta: { + advertiserDomains: serverResponse.body[i].adomain ? serverResponse.body[i].adomain : [] + } }; + bidResponses.push(bidResponse); } return bidResponses; @@ -146,13 +166,8 @@ export const spec = { } } -function getSharedId(bid) { - const id = deepAccess(bid, 'userId.sharedid.id'); - const third = deepAccess(bid, 'userId.sharedid.third'); - return function(kind) { - if (kind === 'id') return id || ''; - return third || ''; - } +function getUsersIds({ userIdAsEids }) { + return (userIdAsEids && userIdAsEids.length !== 0) ? userIdAsEids : []; } function getRr() { diff --git a/modules/bidViewability.js b/modules/bidViewability.js index c3b72cda8d4..545d57940da 100644 --- a/modules/bidViewability.js +++ b/modules/bidViewability.js @@ -4,7 +4,7 @@ import { config } from '../src/config.js'; import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; import { logWarn, isFn, triggerPixel } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; import adapterManager, { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; @@ -70,12 +70,12 @@ export let impressionViewableHandler = (globalModuleConfig, slot, event) => { // trigger respective bidder's onBidViewable handler adapterManager.callBidViewableBidder(respectiveBid.bidder, respectiveBid); // emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels - events.emit(EVENTS.BID_VIEWABLE, respectiveBid); + events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, respectiveBid); } }; export let init = () => { - events.on(EVENTS.AUCTION_INIT, () => { + events.on(CONSTANTS.EVENTS.AUCTION_INIT, () => { // read the config for the module const globalModuleConfig = config.getConfig(MODULE_NAME) || {}; // do nothing if module-config.enabled is not set to true diff --git a/modules/bidViewabilityIO.js b/modules/bidViewabilityIO.js new file mode 100644 index 00000000000..ff7ec70e32c --- /dev/null +++ b/modules/bidViewabilityIO.js @@ -0,0 +1,91 @@ +import { logMessage } from '../src/utils.js'; +import { config } from '../src/config.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; + +const MODULE_NAME = 'bidViewabilityIO'; +const CONFIG_ENABLED = 'enabled'; + +// IAB numbers from: https://support.google.com/admanager/answer/4524488?hl=en +const IAB_VIEWABLE_DISPLAY_TIME = 1000; +const IAB_VIEWABLE_DISPLAY_LARGE_PX = 242000; +export const IAB_VIEWABLE_DISPLAY_THRESHOLD = 0.5 +export const IAB_VIEWABLE_DISPLAY_LARGE_THRESHOLD = 0.3; + +const CLIENT_SUPPORTS_IO = window.IntersectionObserver && window.IntersectionObserverEntry && window.IntersectionObserverEntry.prototype && + 'intersectionRatio' in window.IntersectionObserverEntry.prototype; + +const supportedMediaTypes = [ + 'banner' +]; + +export let isSupportedMediaType = (bid) => { + return supportedMediaTypes.indexOf(bid.mediaType) > -1; +} + +let _logMessage = (message) => { + return logMessage(`${MODULE_NAME}: ${message}`); +} + +// returns options for the iO that detects if the ad is viewable +export let getViewableOptions = (bid) => { + if (bid.mediaType === 'banner') { + return { + root: null, + rootMargin: '0px', + threshold: bid.width * bid.height > IAB_VIEWABLE_DISPLAY_LARGE_PX ? IAB_VIEWABLE_DISPLAY_LARGE_THRESHOLD : IAB_VIEWABLE_DISPLAY_THRESHOLD + } + } +} + +// markViewed returns a function what will be executed when an ad satisifes the viewable iO +export let markViewed = (bid, entry, observer) => { + return () => { + observer.unobserve(entry.target); + events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, bid); + _logMessage(`id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode} was viewed`); + } +} + +// viewCallbackFactory creates the callback used by the viewable IntersectionObserver. +// When an ad comes into view, it sets a timeout for a function to be executed +// when that ad would be considered viewed per the IAB specs. The bid that was rendered +// is passed into the factory, so it can pass it into markViewed, so that it can be included +// in the BID_VIEWABLE event data. If the ad leaves view before the timer goes off, the setTimeout +// is cancelled, an the bid will not be marked as viewed. There's probably some kind of race-ish +// thing going on between IO and setTimeout but this isn't going to be perfect, it's just going to +// be pretty good. +export let viewCallbackFactory = (bid) => { + return (entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + _logMessage(`viewable timer starting for id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode}`); + entry.target.view_tracker = setTimeout(markViewed(bid, entry, observer), IAB_VIEWABLE_DISPLAY_TIME); + } else { + _logMessage(`id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode} is out of view`); + if (entry.target.view_tracker) { + clearTimeout(entry.target.view_tracker); + _logMessage(`viewable timer stopped for id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode}`); + } + } + }); + }; +}; + +export let init = () => { + config.getConfig(MODULE_NAME, conf => { + if (conf[MODULE_NAME][CONFIG_ENABLED] && CLIENT_SUPPORTS_IO) { + // if the module is enabled and the browser supports Intersection Observer, + // then listen to AD_RENDER_SUCCEEDED to setup IO's for supported mediaTypes + events.on(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, ({doc, bid, id}) => { + if (isSupportedMediaType(bid)) { + let viewable = new IntersectionObserver(viewCallbackFactory(bid), getViewableOptions(bid)); + let element = document.getElementById(bid.adUnitCode); + viewable.observe(element); + } + }); + } + }); +} + +init() diff --git a/modules/bidViewabilityIO.md b/modules/bidViewabilityIO.md new file mode 100644 index 00000000000..ad04cf38681 --- /dev/null +++ b/modules/bidViewabilityIO.md @@ -0,0 +1,41 @@ +# Overview + +Module Name: bidViewabilityIO + +Purpose: Emit a BID_VIEWABLE event when a bid becomes viewable using the browsers IntersectionObserver API + +Maintainer: adam.prime@alum.utoronto.ca + +# Description +- This module will trigger a BID_VIEWABLE event which other modules, adapters or publisher code can use to get a sense of viewability +- You can check if this module is part of the final build and whether it is enabled or not by accessing ```pbjs.getConfig('bidViewabilityIO')``` +- Viewability, as measured by this module is not perfect, nor should it be expected to be. +- The module does not require any specific ad server, or an adserver at all. + +# Limitations + +- Currently only supports the banner mediaType +- Assumes that the adUnitCode of the ad is also the id attribute of the element that the ad is rendered into. +- Does not make any attempt to ensure that the ad inside that element is itself visible. It assumes that the publisher is operating in good faith. + +# Params +- enabled [required] [type: boolean, default: false], when set to true, the module will emit BID_VIEWABLE when applicable + +# Example of consuming BID_VIEWABLE event +``` + pbjs.onEvent('bidViewable', function(bid){ + console.log('got bid details in bidViewable event', bid); + }); + +``` + +# Example of using config +``` + pbjs.setConfig({ + bidViewabilityIO: { + enabled: true, + } + }); +``` + +An example implmentation without an ad server can be found in integrationExamples/postbid/bidViewabilityIO_example.html diff --git a/modules/bidfluenceBidAdapter.js b/modules/bidfluenceBidAdapter.js deleted file mode 100644 index f8a1f9ac92f..00000000000 --- a/modules/bidfluenceBidAdapter.js +++ /dev/null @@ -1,131 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const storage = getStorageManager(); -const BIDDER_CODE = 'bidfluence'; - -function stdTimezoneOffset(t) { - const jan = new Date(t.getFullYear(), 0, 1); - const jul = new Date(t.getFullYear(), 6, 1); - return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset()); -} -function dst(t) { - return t.getTimezoneOffset() < stdTimezoneOffset(t); -} -function getBdfTz(d) { - let tz = d.getTimezoneOffset(); - if (dst(d)) { - tz += 60; - } - return tz.toString(); -} -function getUTCDate() { - var m = new Date(); - var dateString = m.getUTCFullYear() + '/' + - ('0' + (m.getUTCMonth() + 1)).slice(-2) + '/' + - ('0' + m.getUTCDate()).slice(-2) + ' ' + - ('0' + m.getUTCHours()).slice(-2) + ':' + - ('0' + m.getUTCMinutes()).slice(-2) + ':' + - ('0' + m.getUTCSeconds()).slice(-2); - - return dateString; -} - -export const spec = { - code: BIDDER_CODE, - isBidRequestValid: function (bid) { - return !!bid.params.placementId || !!bid.params.publisherId; - }, - - buildRequests: function (validBidRequests, bidderRequest) { - const body = document.getElementsByTagName('body')[0]; - const refInfo = bidderRequest.refererInfo; - const gdpr = bidderRequest.gdprConsent; - const vpW = Math.max(window.innerWidth || body.clientWidth || 0) + 2; - const vpH = Math.max(window.innerHeight || body.clientHeight || 0) + 2; - const sr = screen.height > screen.width ? screen.height + 'x' + screen.width + 'x' + screen.colorDepth : screen.width + 'x' + screen.height + 'x' + screen.colorDepth; - - var payload = { - v: '2.0', - azr: true, - ck: storage.cookiesAreEnabled(), - re: refInfo ? refInfo.referer : '', - st: refInfo ? refInfo.stack : [], - tz: getBdfTz(new Date()), - sr: sr, - tm: bidderRequest.timeout, - vp: vpW + 'x' + vpH, - sdt: getUTCDate(), - top: refInfo ? refInfo.reachedTop : false, - gdpr: gdpr ? gdpr.gdprApplies : false, - gdprc: gdpr ? gdpr.consentString : '', - bids: [] - }; - - utils._each(validBidRequests, function (bidRequest) { - var params = bidRequest.params; - var sizes = utils.parseSizesInput(bidRequest.sizes)[0]; - var width = sizes.split('x')[0]; - var height = sizes.split('x')[1]; - - var currentBidPayload = { - bid: bidRequest.bidId, - tid: params.placementId, - pid: params.publisherId, - rp: params.reservePrice || 0, - w: width, - h: height - }; - - payload.bids.push(currentBidPayload); - }); - - const payloadString = JSON.stringify(payload); - return { - method: 'POST', - url: `https://bdf${payload.bids[0].pid}.bidfluence.com/Prebid`, - data: payloadString, - options: { contentType: 'text/plain' } - }; - }, - - interpretResponse: function (serverResponse, bidRequest) { - const bidResponses = []; - const response = serverResponse.body; - - utils._each(response.Bids, function (currentResponse) { - var cpm = currentResponse.Cpm || 0; - - if (cpm > 0) { - const bidResponse = { - requestId: currentResponse.BidId, - cpm: cpm, - width: currentResponse.Width, - height: currentResponse.Height, - creativeId: currentResponse.CreativeId, - ad: currentResponse.Ad, - currency: 'USD', - netRevenue: true, - ttl: 360 - }; - bidResponses.push(bidResponse); - } - }); - - return bidResponses; - }, - - getUserSyncs: function (serverResponses) { - if (serverResponses.userSyncs) { - const syncs = serverResponses.UserSyncs.map((sync) => { - return { - type: sync.Type === 'ifr' ? 'iframe' : 'image', - url: sync.Url - }; - }); - return syncs; - } - } -}; -registerBidder(spec); diff --git a/modules/bidglassBidAdapter.js b/modules/bidglassBidAdapter.js index 44f5cdf4384..3184372881b 100644 --- a/modules/bidglassBidAdapter.js +++ b/modules/bidglassBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { _each, isArray, getBidIdParameter, deepClone, getUniqueIdentifierStr } from '../src/utils.js'; // import {config} from 'src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -69,12 +69,12 @@ export const spec = { let bidglass = window['bidglass']; - utils._each(validBidRequests, function(bid) { - bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); - bid.sizes = bid.sizes.filter(size => utils.isArray(size)); + _each(validBidRequests, function(bid) { + bid.sizes = ((isArray(bid.sizes) && isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); + bid.sizes = bid.sizes.filter(size => isArray(size)); - var adUnitId = utils.getBidIdParameter('adUnitId', bid.params); - var options = utils.deepClone(bid.params); + var adUnitId = getBidIdParameter('adUnitId', bid.params); + var options = deepClone(bid.params); delete options.adUnitId; @@ -96,7 +96,7 @@ export const spec = { // Stuff to send: page URL const bidReq = { - reqId: utils.getUniqueIdentifierStr(), + reqId: getUniqueIdentifierStr(), imps: imps, ref: getReferer(), ori: getOrigins() @@ -125,20 +125,31 @@ export const spec = { interpretResponse: function(serverResponse) { const bidResponses = []; - utils._each(serverResponse.body.bidResponses, function(bid) { - bidResponses.push({ - requestId: bid.requestId, - cpm: parseFloat(bid.cpm), - width: parseInt(bid.width, 10), - height: parseInt(bid.height, 10), - creativeId: bid.creativeId, - dealId: bid.dealId || null, - currency: bid.currency || 'USD', - mediaType: bid.mediaType || 'banner', + _each(serverResponse.body.bidResponses, function(serverBid) { + const bidResponse = { + requestId: serverBid.requestId, + cpm: parseFloat(serverBid.cpm), + width: parseInt(serverBid.width, 10), + height: parseInt(serverBid.height, 10), + creativeId: serverBid.creativeId, + dealId: serverBid.dealId || null, + currency: serverBid.currency || 'USD', + mediaType: serverBid.mediaType || 'banner', netRevenue: true, - ttl: bid.ttl || 10, - ad: bid.ad - }); + ttl: serverBid.ttl || 10, + ad: serverBid.ad, + meta: {} + }; + + if (serverBid.meta) { + let meta = serverBid.meta; + + if (meta.advertiserDomains && meta.advertiserDomains.length) { + bidResponse.meta.advertiserDomains = meta.advertiserDomains; + } + } + + bidResponses.push(bidResponse); }); return bidResponses; diff --git a/modules/bidlabBidAdapter.js b/modules/bidlabBidAdapter.js deleted file mode 100644 index 8f501505a6d..00000000000 --- a/modules/bidlabBidAdapter.js +++ /dev/null @@ -1,112 +0,0 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; - -const BIDDER_CODE = 'bidlab'; -const AD_URL = 'https://service.bidlab.ai/?c=o&m=multi'; -const URL_SYNC = 'https://service.bidlab.ai/?c=o&m=sync'; -const NO_SYNC = true; - -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; - } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl); - case NATIVE: - return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); - default: - return false; - } -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], - noSync: NO_SYNC, - - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - let winTop = window; - let location; - try { - location = new URL(bidderRequest.refererInfo.referer) - winTop = window.top; - } catch (e) { - location = winTop.location; - utils.logMessage(e); - }; - let placements = []; - let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent - } - } - const len = validBidRequests.length; - - for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; - let traff = bid.params.traffic || BANNER - - placements.push({ - placementId: bid.params.placementId, - bidId: bid.bidId, - sizes: bid.mediaTypes && bid.mediaTypes[traff] && bid.mediaTypes[traff].sizes ? bid.mediaTypes[traff].sizes : [], - traffic: traff - }); - if (bid.schain) { - placements.schain = bid.schain; - } - } - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses) => { - if (NO_SYNC) { - return false - } else { - return [{ - type: 'image', - url: URL_SYNC - }]; - } - } - -}; - -registerBidder(spec); diff --git a/modules/bidphysicsBidAdapter.js b/modules/bidphysicsBidAdapter.js deleted file mode 100644 index b6b5690ede5..00000000000 --- a/modules/bidphysicsBidAdapter.js +++ /dev/null @@ -1,134 +0,0 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; -import {BANNER} from '../src/mediaTypes.js'; - -const ENDPOINT_URL = 'https://exchange.bidphysics.com/auction'; - -const DEFAULT_BID_TTL = 30; -const DEFAULT_CURRENCY = 'USD'; -const DEFAULT_NET_REVENUE = true; - -export const spec = { - code: 'bidphysics', - aliases: ['yieldlift'], - supportedMediaTypes: [BANNER], - - isBidRequestValid: function (bid) { - return (!!bid.params.unitId && typeof bid.params.unitId === 'string') || - (!!bid.params.networkId && typeof bid.params.networkId === 'string') || - (!!bid.params.publisherId && typeof bid.params.publisherId === 'string'); - }, - - buildRequests: function (validBidRequests, bidderRequest) { - if (!validBidRequests || !bidderRequest) { - return; - } - const publisherId = validBidRequests[0].params.publisherId; - const networkId = validBidRequests[0].params.networkId; - const impressions = validBidRequests.map(bidRequest => ({ - id: bidRequest.bidId, - banner: { - format: bidRequest.sizes.map(sizeArr => ({ - w: sizeArr[0], - h: sizeArr[1] - })) - }, - ext: { - bidphysics: { - unitId: bidRequest.params.unitId - } - } - })); - - const openrtbRequest = { - id: bidderRequest.auctionId, - imp: impressions, - site: { - domain: window.location.hostname, - page: window.location.href, - ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null - }, - ext: { - bidphysics: { - publisherId: publisherId, - networkId: networkId, - } - } - }; - - // apply gdpr - if (bidderRequest.gdprConsent) { - openrtbRequest.regs = {ext: {gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0}}; - openrtbRequest.user = {ext: {consent: bidderRequest.gdprConsent.consentString}}; - } - - const payloadString = JSON.stringify(openrtbRequest); - return { - method: 'POST', - url: ENDPOINT_URL, - data: payloadString, - }; - }, - - interpretResponse: function (serverResponse, request) { - const bidResponses = []; - const response = (serverResponse || {}).body; - // response is always one seat (bidphysics) with (optional) bids for each impression - if (response && response.seatbid && response.seatbid.length === 1 && response.seatbid[0].bid && response.seatbid[0].bid.length) { - response.seatbid[0].bid.forEach(bid => { - bidResponses.push({ - requestId: bid.impid, - cpm: bid.price, - width: bid.w, - height: bid.h, - ad: bid.adm, - ttl: DEFAULT_BID_TTL, - creativeId: bid.crid, - netRevenue: DEFAULT_NET_REVENUE, - currency: DEFAULT_CURRENCY, - }) - }) - } else { - utils.logInfo('bidphysics.interpretResponse :: no valid responses to interpret'); - } - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - utils.logInfo('bidphysics.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); - let syncs = []; - - if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { - return syncs; - } - - serverResponses.forEach(resp => { - const userSync = utils.deepAccess(resp, 'body.ext.usersync'); - if (userSync) { - let syncDetails = []; - Object.keys(userSync).forEach(key => { - const value = userSync[key]; - if (value.syncs && value.syncs.length) { - syncDetails = syncDetails.concat(value.syncs); - } - }); - syncDetails.forEach(syncDetails => { - syncs.push({ - type: syncDetails.type === 'iframe' ? 'iframe' : 'image', - url: syncDetails.url - }); - }); - - if (!syncOptions.iframeEnabled) { - syncs = syncs.filter(s => s.type !== 'iframe') - } - if (!syncOptions.pixelEnabled) { - syncs = syncs.filter(s => s.type !== 'image') - } - } - }); - utils.logInfo('bidphysics.getUserSyncs result=%o', syncs); - return syncs; - }, - -}; -registerBidder(spec); diff --git a/modules/decenteradsBidAdapter.js b/modules/bidscubeBidAdapter.js similarity index 80% rename from modules/decenteradsBidAdapter.js rename to modules/bidscubeBidAdapter.js index 823a59a3768..951bd97d255 100644 --- a/modules/decenteradsBidAdapter.js +++ b/modules/bidscubeBidAdapter.js @@ -1,17 +1,17 @@ +import { logMessage, getWindowLocation } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js' -import * as utils from '../src/utils.js' -const BIDDER_CODE = 'decenterads' -const URL = 'https://supply.decenterads.com/?c=o&m=multi' -const URL_SYNC = 'https://supply.decenterads.com/?c=o&m=cookie' +const BIDDER_CODE = 'bidscube' +const URL = 'https://supply.bidscube.com/?c=o&m=multi' +const URL_SYNC = 'https://supply.bidscube.com/?c=o&m=cookie' export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: function (opts) { - return Boolean(opts.bidId && opts.params && !isNaN(opts.params.placementId)) + return Boolean(opts.bidId && opts.params && !isNaN(parseInt(opts.params.placementId))) }, buildRequests: function (validBidRequests) { @@ -20,9 +20,9 @@ export const spec = { try { window.top.location.toString() winTop = window.top - } catch (e) { utils.logMessage(e) } + } catch (e) { logMessage(e) } - const location = utils.getWindowLocation() + const location = getWindowLocation() const placements = [] for (let i = 0; i < validBidRequests.length; i++) { @@ -31,7 +31,8 @@ export const spec = { placements.push({ placementId: p.params.placementId, bidId: p.bidId, - traffic: p.params.traffic || BANNER + traffic: p.params.traffic || BANNER, + allParams: JSON.stringify(p) }) } @@ -57,7 +58,6 @@ export const spec = { for (let i = 0; i < body.length; i++) { const item = body[i] if (isBidResponseValid(item)) { - delete item.mediaType response.push(item) } } diff --git a/modules/bidscubeBidAdapter.md b/modules/bidscubeBidAdapter.md new file mode 100644 index 00000000000..5f3972726ec --- /dev/null +++ b/modules/bidscubeBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: BidsCube Bidder Adapter +Module Type: Bidder Adapter +Maintainer: publishers@bidscube.com +``` + +# Description + +Module that connects to BidsCube' demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'bidscube', + params: { + placementId: 0, + traffic: 'banner' + } + }] + }]; +``` diff --git a/modules/big-richmediaBidAdapter.js b/modules/big-richmediaBidAdapter.js new file mode 100644 index 00000000000..cd8b2462eb8 --- /dev/null +++ b/modules/big-richmediaBidAdapter.js @@ -0,0 +1,117 @@ +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {spec as baseAdapter} from './appnexusBidAdapter.js'; // eslint-disable-line prebid/validate-imports + +const BIDDER_CODE = 'big-richmedia'; + +const metadataByRequestId = {}; + +export const spec = { + version: '1.4.0', + code: BIDDER_CODE, + gvlid: baseAdapter.GVLID, // use base adapter gvlid + supportedMediaTypes: [ BANNER, VIDEO ], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!baseAdapter.isBidRequestValid) { return true; } + return baseAdapter.isBidRequestValid(bid); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests, bidderRequest) { + if (!baseAdapter.buildRequests) { return []; } + + const publisherId = config.getConfig('bigRichmedia.publisherId'); + if (typeof publisherId !== 'string') { return []; } + + bidRequests.forEach(bidRequest => { + if (bidRequest.params.format === 'skin' && bidRequest.mediaTypes.banner) { + bidRequest.mediaTypes.banner.sizes.push([1800, 1000]); + } + metadataByRequestId[bidRequest.bidId] = { placementId: bidRequest.adUnitCode, bidder: bidRequest.bidder }; + }); + return baseAdapter.buildRequests(bidRequests, bidderRequest); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, params) { + const publisherId = config.getConfig('bigRichmedia.publisherId'); + if (typeof publisherId !== 'string') { return []; } + + const bids = baseAdapter.interpretResponse(serverResponse, params); + bids.forEach(bid => { + const { placementId, bidder } = metadataByRequestId[bid.requestId] || {}; + const { width = 1, height = 1, ad, creativeId = '', cpm, vastXml, vastUrl } = bid; + const bidRequest = params.bidderRequest.bids.find(({ bidId }) => bidId === bid.requestId); + const format = (bidRequest && bidRequest.params && bidRequest.params.format) || 'video-sticky-footer'; + const isReplayable = bidRequest && bidRequest.params && bidRequest.params.isReplayable; + const customSelector = bidRequest && bidRequest.params && bidRequest.params.customSelector; + const renderParams = { + adm: ad, + vastXml, + vastUrl, + width, + height, + placementId, + bidId: bid.requestId, + creativeId: `${creativeId}`, + bidder, + cpm, + format, + customSelector, + isReplayable + }; + const encoded = window.btoa(JSON.stringify(renderParams)); + bid.ad = ` + `; + + if (bid.mediaType !== 'banner') { // in case this is a video + bid.mediaType = 'banner'; + delete bid.renderer; + delete bid.vastUrl; + delete bid.vastXml; + bid.width = 1; + bid.height = 1; + } + }); + return bids; + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (!baseAdapter.getUserSyncs) { return []; } + return baseAdapter.getUserSyncs(syncOptions, responses, gdprConsent); + }, + + transformBidParams: function (params, isOpenRtb) { + if (!baseAdapter.transformBidParams) { return params; } + return baseAdapter.transformBidParams(params, isOpenRtb); + }, + + /** + * Add element selector to javascript tracker to improve native viewability + * @param {Bid} bid + */ + onBidWon: function (bid) { + if (!baseAdapter.onBidWon) { return; } + baseAdapter.onBidWon(bid); + } +} + +registerBidder(spec); diff --git a/modules/big-richmediaBidAdapter.md b/modules/big-richmediaBidAdapter.md new file mode 100644 index 00000000000..26f77e527fb --- /dev/null +++ b/modules/big-richmediaBidAdapter.md @@ -0,0 +1,82 @@ +# Overview + +``` +Module Name: BI.Garage Rich Media +Module Type: Bidder Adapter +Maintainer: mediaconsortium-develop@bi.garage.co.jp +``` + +# Description + +Module which renders richmedia demand from a Xandr seat + +### Global configuration + +```javascript +pbjs.setConfig({ + debug: false, + // …, + bigRichmedia: { + publisherId: 'A7FN99NZ98F5ZD4G', // Required + }, +}); +``` + +# AdUnit Configuration +```javascript +var adUnits = [ + // Skin adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'big-richmedia', + params: { + placementId: 12345, + format: 'skin' // This will automatically add 1800x1000 size to banner mediaType + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[300, 250]], + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream', + // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. + // To note - appnexus supports additional values for our system that are not part of the ORTB spec. If you want + // to use these values, they will have to be declared in the bids[].params.video object instead using the appnexus syntax. + // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will + // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. + minduration: 1, + maxduration: 60, + skip: 0, // 1 - true, 0 - false + skipafter: 5, + playbackmethod: [2], // note - we only support options 1-4 at this time + api: [1,2,3] // note - option 6 is not supported at this time + } + }, + bids: [ + { + bidder: 'big-richmedia', + params: { + placementId: 12345, + video: { + skippable: true, + playback_method: 'auto_play_sound_off' + }, + format: 'video-sticky-footer', // or 'video-sticky-top' + isReplayable: true // Default to false - choose if the video should be replayable or not. + customSelector: '#nav-bar' // custom selector for navbar + } + } + ] + } +]; +``` diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 2af9a7afed2..6223626834d 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -1,8 +1,7 @@ +import { logMessage, getDNT, deepSetValue, deepAccess, _map, logWarn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; import {config} from '../src/config.js'; - const BIDDER_CODE = 'bizzclick'; const ACCOUNTID_MACROS = '[account_id]'; const URL_ENDPOINT = `https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=${ACCOUNTID_MACROS}`; @@ -39,11 +38,9 @@ const NATIVE_PARAMS = { } }; const NATIVE_VERSION = '1.2'; - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - /** * Determines whether or not the given bid request is valid. * @@ -53,7 +50,6 @@ export const spec = { isBidRequestValid: (bid) => { return Boolean(bid.params.accountId) && Boolean(bid.params.placementId) }, - /** * Make a server request from the list of BidRequests. * @@ -64,7 +60,6 @@ export const spec = { if (validBidRequests && validBidRequests.length === 0) return [] let accuontId = validBidRequests[0].params.accountId; const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); - let winTop = window; let location; try { @@ -72,9 +67,8 @@ export const spec = { winTop = window.top; } catch (e) { location = winTop.location; - utils.logMessage(e); + logMessage(e); }; - let bids = []; for (let bidRequest of validBidRequests) { let impObject = prepareImpObject(bidRequest); @@ -86,7 +80,7 @@ export const spec = { device: { w: winTop.screen.width, h: winTop.screen.height, - dnt: utils.getDNT() ? 1 : 0, + dnt: getDNT() ? 1 : 0, language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', }, site: { @@ -94,26 +88,50 @@ export const spec = { host: location.host }, source: { - tid: bidRequest.transactionId + tid: bidRequest.transactionId, + ext: { + schain: {} + } }, regs: { coppa: config.getConfig('coppa') === true ? 1 : 0, - ext: {} }, user: { ext: {} }, + ext: { + ts: Date.now() + }, tmax: bidRequest.timeout, 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.gdprConsent && bidRequest.gdprConsent.gdprApplies) { - utils.deepSetValue(data, 'regs.ext.gdpr', bidRequest.gdprConsent.gdprApplies ? 1 : 0); - utils.deepSetValue(data, 'user.ext.consent', bidRequest.gdprConsent.consentString); + deepSetValue(data, 'regs.ext.gdpr', bidRequest.gdprConsent.gdprApplies ? 1 : 0); + deepSetValue(data, 'user.ext.consent', bidRequest.gdprConsent.consentString); } if (bidRequest.uspConsent !== undefined) { - utils.deepSetValue(data, 'regs.ext.us_privacy', bidRequest.uspConsent); + deepSetValue(data, 'regs.ext.us_privacy', bidRequest.uspConsent); } } bids.push(data) @@ -124,7 +142,6 @@ export const spec = { data: bids }; }, - /** * Unpack the response from the server into a list of bids. * @@ -134,11 +151,9 @@ export const spec = { interpretResponse: (serverResponse) => { if (!serverResponse || !serverResponse.body) return [] let bizzclickResponse = serverResponse.body; - let bids = []; for (let response of bizzclickResponse) { let mediaType = response.seatbid[0].bid[0].ext && response.seatbid[0].bid[0].ext.mediaType ? response.seatbid[0].bid[0].ext.mediaType : BANNER; - let bid = { requestId: response.id, cpm: response.seatbid[0].bid[0].price, @@ -152,6 +167,11 @@ export const spec = { mediaType: mediaType }; + bid.meta = {}; + if (response.seatbid[0].bid[0].adomain && response.seatbid[0].bid[0].adomain.length > 0) { + bid.meta.advertiserDomains = response.seatbid[0].bid[0].adomain; + } + switch (mediaType) { case VIDEO: bid.vastXml = response.seatbid[0].bid[0].adm @@ -163,14 +183,11 @@ export const spec = { default: bid.ad = response.seatbid[0].bid[0].adm } - bids.push(bid); } - return bids; }, }; - /** * Determine type of request * @@ -179,9 +196,8 @@ export const spec = { * @returns {boolean} */ const checkRequestType = (bidRequest, type) => { - return (typeof utils.deepAccess(bidRequest, `mediaTypes.${type}`) !== 'undefined'); + return (typeof deepAccess(bidRequest, `mediaTypes.${type}`) !== 'undefined'); } - const parseNative = admObject => { const { assets, link, imptrackers, jstracker } = admObject.native; const result = { @@ -197,10 +213,8 @@ const parseNative = admObject => { result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; } }); - return result; } - const prepareImpObject = (bidRequest) => { let impObject = { id: bidRequest.transactionId, @@ -223,14 +237,12 @@ const prepareImpObject = (bidRequest) => { } return impObject }; - const addNativeParameters = bidRequest => { let impObject = { id: bidRequest.transactionId, ver: NATIVE_VERSION, }; - - const assets = utils._map(bidRequest.mediaTypes.native, (bidParams, key) => { + const assets = _map(bidRequest.mediaTypes.native, (bidParams, key) => { const props = NATIVE_PARAMS[key]; const asset = { required: bidParams.required & 1, @@ -239,34 +251,27 @@ const addNativeParameters = bidRequest => { asset.id = props.id; let wmin, hmin; let aRatios = bidParams.aspect_ratios; - if (aRatios && aRatios[0]) { aRatios = aRatios[0]; wmin = aRatios.min_width || 0; hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; } - if (bidParams.sizes) { const sizes = flatten(bidParams.sizes); wmin = sizes[0]; hmin = sizes[1]; } - asset[props.name] = {} - if (bidParams.len) asset[props.name]['len'] = bidParams.len; if (props.type) asset[props.name]['type'] = props.type; if (wmin) asset[props.name]['wmin'] = wmin; if (hmin) asset[props.name]['hmin'] = hmin; - return asset; } }).filter(Boolean); - impObject.assets = assets; return impObject } - const addBannerParameters = (bidRequest) => { let bannerObject = {}; const size = parseSizes(bidRequest, 'banner'); @@ -274,7 +279,6 @@ const addBannerParameters = (bidRequest) => { bannerObject.h = size[1]; return bannerObject; }; - const parseSizes = (bid, mediaType) => { let mediaTypes = bid.mediaTypes; if (mediaType === 'video') { @@ -284,7 +288,7 @@ const parseSizes = (bid, mediaType) => { mediaTypes.video.w, mediaTypes.video.h ]; - } else if (Array.isArray(utils.deepAccess(bid, 'mediaTypes.video.playerSize')) && bid.mediaTypes.video.playerSize.length === 1) { + } else if (Array.isArray(deepAccess(bid, 'mediaTypes.video.playerSize')) && bid.mediaTypes.video.playerSize.length === 1) { size = bid.mediaTypes.video.playerSize[0]; } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0 && Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1) { size = bid.sizes[0]; @@ -297,30 +301,24 @@ const parseSizes = (bid, mediaType) => { } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { sizes = bid.sizes } else { - utils.logWarn('no sizes are setup or found'); + logWarn('no sizes are setup or found'); } - return sizes } - const addVideoParameters = (bidRequest) => { let videoObj = {}; let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'] - for (let param of supportParamsList) { if (bidRequest.mediaTypes.video[param] !== undefined) { videoObj[param] = bidRequest.mediaTypes.video[param]; } } - const size = parseSizes(bidRequest, 'video'); videoObj.w = size[0]; videoObj.h = size[1]; return videoObj; } - const flatten = arr => { return [].concat(...arr); } - registerBidder(spec); diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js new file mode 100644 index 00000000000..45b6c46c2df --- /dev/null +++ b/modules/bliinkBidAdapter.js @@ -0,0 +1,336 @@ +// eslint-disable-next-line prebid/validate-imports +// eslint-disable-next-line prebid/validate-imports +import {registerBidder} from '../src/adapters/bidderFactory.js' +export const BIDDER_CODE = 'bliink' +export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/delivery' +export const BLIINK_ENDPOINT_ENGINE_VAST = 'https://engine.bliink.io/vast' +export const BLIINK_ENDPOINT_COOKIE_SYNC = 'https://cookiesync.api.bliink.io' +export const META_KEYWORDS = 'keywords' +export const META_DESCRIPTION = 'description' + +const VIDEO = 'video' +const BANNER = 'banner' + +const supportedMediaTypes = [BANNER, VIDEO] +const aliasBidderCode = ['bk'] + +export function getMetaList(name) { + if (!name || name.length === 0) return [] + + return [ + { + key: 'name', + value: name, + }, + { + key: 'name*', + value: name, + }, + { + key: 'itemprop*', + value: name, + }, + { + key: 'property', + value: `'og:${name}'`, + }, + { + key: 'property', + value: `'twitter:${name}'`, + }, + { + key: 'property', + value: `'article:${name}'`, + }, + ] +} + +export function getOneMetaValue(query) { + const metaEl = document.querySelector(query) + + if (metaEl && metaEl.content) { + return metaEl.content + } + + return null +} + +export function getMetaValue(name) { + const metaList = getMetaList(name) + for (let i = 0; i < metaList.length; i++) { + const meta = metaList[i]; + const metaValue = getOneMetaValue(`meta[${meta.key}=${meta.value}]`); + if (metaValue) { + return metaValue + } + } + return '' +} + +export function getKeywords() { + const metaKeywords = getMetaValue(META_KEYWORDS) + if (metaKeywords) { + const keywords = [ + ...metaKeywords.split(','), + ] + + if (keywords && keywords.length > 0) { + return keywords + .filter((value) => value) + .map((value) => value.trim()) + } + } + + return [] +} + +export const parseXML = (content) => { + if (typeof content !== 'string' || content.length === 0) return null + + const parser = new DOMParser() + let xml; + + try { + xml = parser.parseFromString(content, 'text/xml') + } catch (e) {} + + if (xml && + xml.getElementsByTagName('VAST')[0] && + xml.getElementsByTagName('VAST')[0].tagName === 'VAST') { + return xml + } + + return null +} + +/** + * @param bidRequest + * @param bliinkCreative + * @return {{cpm, netRevenue: boolean, requestId, width: (*|number), currency, ttl: number, creativeId, height: (*|number)} & {mediaType: string, vastXml}} + */ +export const buildBid = (bidRequest, bliinkCreative) => { + if (!bidRequest && !bliinkCreative) return null + + const body = { + requestId: bidRequest.bidId, + currency: bliinkCreative.currency, + cpm: bliinkCreative.price, + creativeId: bliinkCreative.creativeId, + width: (bidRequest.sizes && bidRequest.sizes[0][0]) || 1, + height: (bidRequest.sizes && bidRequest.sizes[0][1]) || 1, + netRevenue: false, + ttl: 3600, + } + + // eslint-disable-next-line no-mixed-operators + if ((bliinkCreative) && bidRequest && + // eslint-disable-next-line no-mixed-operators + !bidRequest.bidId || + !bidRequest.sizes || + !bidRequest.params || + !(bidRequest.params.placement) + ) return null + + delete bidRequest['bids'] + + switch (bliinkCreative.media_type) { + case VIDEO: + return Object.assign(body, { + mediaType: VIDEO, + vastXml: bliinkCreative.content, + }) + case BANNER: + return Object.assign(body, { + mediaType: BANNER, + ad: (bliinkCreative && bliinkCreative.content && bliinkCreative.content.creative && bliinkCreative.content.creative.adm) || '', + }) + default: + break; + } +} + +/** + * @description Verify the the AdUnits.bids, respond with true (valid) or false (invalid). + * + * @param bid + * @return boolean + */ +export const isBidRequestValid = (bid) => { + return !(!bid || !bid.params || !bid.params.placement || !bid.params.tagId) +} + +/** + * @description Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. + * + * @param _[] + * @param bidderRequest + * @return {{ method: string, url: string } | null} + */ +export const buildRequests = (_, bidderRequest) => { + if (!bidderRequest) return null + + let data = { + pageUrl: bidderRequest.refererInfo.referer, + pageDescription: getMetaValue(META_DESCRIPTION), + keywords: getKeywords().join(','), + gdpr: false, + gdpr_consent: '', + pageTitle: document.title, + } + + const endPoint = bidderRequest.bids[0].params.placement === VIDEO ? BLIINK_ENDPOINT_ENGINE_VAST : BLIINK_ENDPOINT_ENGINE + + const params = { + bidderRequestId: bidderRequest.bidderRequestId, + bidderCode: bidderRequest.bidderCode, + bids: bidderRequest.bids, + refererInfo: bidderRequest.refererInfo, + } + + if (bidderRequest.gdprConsent) { + data = Object.assign(data, { + gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies, + gdpr_consent: bidderRequest.gdprConsent.consentString + }) + } + + if (bidderRequest.bids && bidderRequest.bids.length > 0 && bidderRequest.bids[0].sizes && bidderRequest.bids[0].sizes[0]) { + data = Object.assign(data, { + width: bidderRequest.bids[0].sizes[0][0], + height: bidderRequest.bids[0].sizes[0][1] + }) + + return { + method: 'GET', + url: `${endPoint}/${bidderRequest.bids[0].params.tagId}`, + data: data, + params: params, + } + } + + return null +} + +/** + * @description Parse the response (from buildRequests) and generate one or more bid objects. + * + * @param serverResponse + * @param request + * @return + */ +const interpretResponse = (serverResponse, request) => { + if ((serverResponse && serverResponse.mode === 'no-ad')) { + return [] + } + + const body = serverResponse.body + const serverBody = request.params + + const xml = parseXML(body) + + let creative; + + switch (serverBody.bids[0].params.placement) { + case xml && VIDEO: + const price = xml.getElementsByTagName('Price') && xml.getElementsByTagName('Price')[0] + const currency = xml.getElementsByTagName('Currency') && xml.getElementsByTagName('Currency')[0] + const creativeId = xml.getElementsByTagName('CreativeId') && xml.getElementsByTagName('CreativeId')[0] + + creative = { + content: body, + price: (price && price.textContent) || 0, + currency: (currency && currency.textContent) || 'EUR', + creativeId: creativeId || 0, + media_type: 'video', + } + + return buildBid(serverBody.bids[0], creative) + case BANNER: + if (body) { + creative = { + content: body, + price: body.price, + currency: body.currency, + creativeId: 0, + media_type: 'banner', + } + + return buildBid(serverBody.bids[0], creative) + } + + break + default: + break + } +} + +/** + * @description If the publisher allows user-sync activity, the platform will call this function and the adapter may register pixels and/or iframe user syncs. For more information, see Registering User Syncs below + * @param syncOptions + * @param serverResponses + * @param gdprConsent + * @return {[{type: string, url: string}]|*[]} + */ +const getUserSyncs = (syncOptions, serverResponses, gdprConsent) => { + let syncs = [] + + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + if (gdprConsent) { + const gdprParams = `consentString=${gdprConsent.consentString}` + const smartCallbackURL = encodeURIComponent(`${BLIINK_ENDPOINT_COOKIE_SYNC}/cookiesync?partner=smart&uid=[sas_uid]`) + const azerionCallbackURL = encodeURIComponent(`${BLIINK_ENDPOINT_COOKIE_SYNC}/cookiesync?partner=azerion&uid={PUB_USER_ID}`) + const appnexusCallbackURL = encodeURIComponent(`${BLIINK_ENDPOINT_COOKIE_SYNC}/cookiesync?partner=azerion&uid=$UID`) + return [ + { + type: 'script', + url: 'https://prg.smartadserver.com/ac?out=js&nwid=3392&siteid=305791&pgname=rg&fmtid=81127&tgt=[sas_target]&visit=m&tmstp=[timestamp]&clcturl=[countgo]' + }, + { + type: 'image', + url: `https://sync.smartadserver.com/getuid?nwid=3392&${gdprParams}&url=${smartCallbackURL}`, + }, + { + type: 'image', + url: `https://ad.360yield.com/server_match?partner_id=1531&${gdprParams}&r=${azerionCallbackURL}`, + }, + { + type: 'image', + url: `https://ads.stickyadstv.com/auto-user-sync?${gdprParams}`, + }, + { + type: 'image', + url: `https://cookiesync.api.bliink.io/getuid?url=https%3A%2F%2Fvisitor.omnitagjs.com%2Fvisitor%2Fsync%3Fuid%3D1625272249969090bb9d544bd6d8d645%26name%3DBLIINK%26visitor%3D%24UID%26external%3Dtrue&${gdprParams}`, + }, + { + type: 'image', + url: `https://cookiesync.api.bliink.io/getuid?url=https://pixel.advertising.com/ups/58444/sync?&gdpr=1&gdpr_consent=${gdprConsent.consentString}&redir=true&uid=$UID`, + }, + { + type: 'image', + url: `https://ups.analytics.yahoo.com/ups/58499/occ?gdpr=1&gdpr_consent=${gdprConsent.consentString}`, + }, + { + type: 'image', + url: `https://secure.adnxs.com/getuid?${appnexusCallbackURL}`, + }, + ] + } + } + + return syncs; +} + +/** + * @type {{interpretResponse: interpretResponse, code: string, aliases: string[], getUserSyncs: getUserSyncs, buildRequests: buildRequests, onTimeout: onTimeout, onSetTargeting: onSetTargeting, isBidRequestValid: isBidRequestValid, onBidWon: onBidWon}} + */ +export const spec = { + code: BIDDER_CODE, + aliases: aliasBidderCode, + supportedMediaTypes: supportedMediaTypes, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +} + +registerBidder(spec) diff --git a/modules/bliinkBidAdapter.md b/modules/bliinkBidAdapter.md new file mode 100644 index 00000000000..af7aee3a1ae --- /dev/null +++ b/modules/bliinkBidAdapter.md @@ -0,0 +1,94 @@ +# Overview + +``` +Module Name: BLIINK Bidder Adapter +Module Type: Bidder Adapter +Maintainer: samuel@bliink.io | jonathan@bliink.io +gdpr_supported: true +tcf2_supported: true +media_types: banner, native, video +``` + +# Description + +Module that connects to BLIINK demand sources to fetch bids. + +# Test Parameters + +## Sample Banner Ad Unit + +```js +const adUnits = [ + { + code: '/19968336/test', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'bliink', + params: { + placement: 'banner', + tagId: '41' + } + } + ] + } +] +``` + +## Sample Instream Video Ad Unit + +```js +const adUnits = [ + { + code: '/19968336/prebid_cache_video_adunit', + sizes: [[640,480]], + mediaType: 'video', + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640,480]], + } + }, + bids: [ + { + bidder: 'bliink', + params: { + tagId: '41', + placement: 'video', + } + } + ] + } +] +``` + +## Sample outstream Video Ad Unit + +```js +const adUnits = [ + { + code: '/19968336/prebid_cache_video_adunit', + sizes: [[640,480]], + mediaType: 'video', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640,480]], + } + }, + bids: [ + { + bidder: 'bliink', + params: { + tagId: '41', + placement: 'video', + } + } + ] + } +] +``` diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js index cac993b0f8c..03fb0b92c8f 100644 --- a/modules/bluebillywigBidAdapter.js +++ b/modules/bluebillywigBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +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'; @@ -8,7 +8,7 @@ import { createEidsArray } from './userId/eids.js'; const DEV_MODE = window.location.search.match(/bbpbs_debug=true/); -// Blue Billywig Constants +// Blue Billywig Constants const BB_CONSTANTS = { BIDDER_CODE: 'bluebillywig', AUCTION_URL: '$$URL_STARTpbs.bluebillywig.com/openrtb2/auction?pub=$$PUBLICATION', @@ -43,7 +43,7 @@ const BB_HELPERS = { if (!request.device.h) request.device.h = window.innerHeight; }, addSchain: function(request, validBidRequests) { - const schain = utils.deepAccess(validBidRequests, '0.schain'); + const schain = deepAccess(validBidRequests, '0.schain'); if (schain) request.source.ext = { schain: schain }; }, addCurrency: function(request) { @@ -52,11 +52,11 @@ const BB_HELPERS = { else if (Array.isArray(adServerCur) && adServerCur.length) request.cur = [adServerCur[0]]; }, addUserIds: function(request, validBidRequests) { - const bidUserId = utils.deepAccess(validBidRequests, '0.userId'); + const bidUserId = deepAccess(validBidRequests, '0.userId'); const eids = createEidsArray(bidUserId); if (eids.length) { - utils.deepSetValue(request, 'user.ext.eids', eids); + deepSetValue(request, 'user.ext.eids', eids); } }, substituteUrl: function (url, publication, renderer) { @@ -72,7 +72,7 @@ const BB_HELPERS = { return BB_HELPERS.substituteUrl(BB_CONSTANTS.RENDERER_URL, publication, renderer); }, transformVideoParams: function(videoParams, videoParamsExt) { - videoParams = utils.deepClone(videoParams); + videoParams = deepClone(videoParams); let playerSize = videoParams.playerSize || [BB_CONSTANTS.DEFAULT_WIDTH, BB_CONSTANTS.DEFAULT_HEIGHT]; if (Array.isArray(playerSize[0])) playerSize = playerSize[0]; @@ -105,8 +105,8 @@ const BB_HELPERS = { ttl: BB_CONSTANTS.DEFAULT_TTL }; - const extPrebidTargeting = utils.deepAccess(bid, 'ext.prebid.targeting'); - const extPrebidCache = utils.deepAccess(bid, 'ext.prebid.cache'); + const extPrebidTargeting = deepAccess(bid, 'ext.prebid.targeting'); + const extPrebidCache = deepAccess(bid, 'ext.prebid.cache'); if (extPrebidCache && typeof extPrebidCache.vastXml === 'object' && extPrebidCache.vastXml.cacheId && extPrebidCache.vastXml.url) { bidObject.videoCacheKey = extPrebidCache.vastXml.cacheId; @@ -122,7 +122,8 @@ const BB_HELPERS = { if (!bidObject.vastUrl && bid.nurl && !bid.adm) { // ad markup is on win notice url, and adm is ommited according to OpenRTB 2.5 bidObject.vastUrl = bid.nurl; } - + bidObject.meta = bid.meta || {}; + if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } return bidObject; }, }; @@ -138,12 +139,12 @@ const BB_RENDERER = { else if (bid.vastUrl) config.vastUrl = bid.vastUrl; if (!bid.vastXml && !bid.vastUrl) { - utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: No vastXml or vastUrl on bid, bailing...`); + logWarn(`${BB_CONSTANTS.BIDDER_CODE}: No vastXml or vastUrl on bid, bailing...`); return; } if (!(window.bluebillywig && window.bluebillywig.renderers)) { - utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: renderer code failed to initialize...`); + logWarn(`${BB_CONSTANTS.BIDDER_CODE}: renderer code failed to initialize...`); return; } @@ -152,7 +153,7 @@ const BB_RENDERER = { const renderer = find(window.bluebillywig.renderers, r => r._id === rendererId); if (renderer) renderer.bootstrap(config, ele, bid.rendererSettings || {}); - else utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Couldn't find a renderer with ${rendererId}`); + else logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Couldn't find a renderer with ${rendererId}`); }, newRenderer: function(rendererUrl, adUnitCode) { const renderer = Renderer.install({ @@ -164,7 +165,7 @@ const BB_RENDERER = { try { renderer.setRender(BB_RENDERER.outstreamRender); } catch (err) { - utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Error tying to setRender on renderer`, err); + logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Error tying to setRender on renderer`, err); } return renderer; @@ -188,70 +189,70 @@ export const spec = { const rendererRegex = /^[\w+_]+$/; if (!bid.params) { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no params set on bid. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: no params set on bid. Rejecting bid: `, bid); return false; } if (!bid.params.hasOwnProperty('publicationName') || typeof bid.params.publicationName !== 'string') { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no publicationName specified in bid params, or it's not a string. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: no publicationName specified in bid params, or it's not a string. Rejecting bid: `, bid); return false; } else if (!publicationNameRegex.test(bid.params.publicationName)) { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: publicationName must be in format 'publication' or 'publication.environment'. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: publicationName must be in format 'publication' or 'publication.environment'. Rejecting bid: `, bid); return false; } if ((!bid.params.hasOwnProperty('rendererCode') || typeof bid.params.rendererCode !== 'string')) { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no rendererCode was specified in bid params. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: no rendererCode was specified in bid params. Rejecting bid: `, bid); return false; } else if (!rendererRegex.test(bid.params.rendererCode)) { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: rendererCode must be alphanumeric, including underscores. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: rendererCode must be alphanumeric, including underscores. Rejecting bid: `, bid); return false; } if (!bid.params.accountId) { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no accountId specified in bid params. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: no accountId specified in bid params. Rejecting bid: `, bid); return false; } if (bid.params.hasOwnProperty('connections')) { if (!Array.isArray(bid.params.connections)) { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: connections is not of type array. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: connections is not of type array. Rejecting bid: `, bid); return false; } else { for (let i = 0; i < bid.params.connections.length; i++) { if (!bid.params.hasOwnProperty(bid.params.connections[i])) { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: connection specified in params.connections, but not configured in params. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: connection specified in params.connections, but not configured in params. Rejecting bid: `, bid); return false; } } } } else { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no connections specified in bid. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: no connections specified in bid. Rejecting bid: `, bid); return false; } if (bid.params.hasOwnProperty('video') && (bid.params.video === null || typeof bid.params.video !== 'object')) { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: params.video must be of type object. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: params.video must be of type object. Rejecting bid: `, bid); return false; } if (bid.params.hasOwnProperty('rendererSettings') && (bid.params.rendererSettings === null || typeof bid.params.rendererSettings !== 'object')) { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: params.rendererSettings must be of type object. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: params.rendererSettings must be of type object. Rejecting bid: `, bid); return false; } if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no context specified in bid. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: no context specified in bid. Rejecting bid: `, bid); return false; } if (bid.mediaTypes[VIDEO].context !== 'outstream') { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: video.context is invalid, must be "outstream". Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: video.context is invalid, must be "outstream". Rejecting bid: `, bid); return false; } } else { - utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: mediaTypes or mediaTypes.video is not specified. Rejecting bid: `, bid); + logError(`${BB_CONSTANTS.BIDDER_CODE}: mediaTypes or mediaTypes.video is not specified. Rejecting bid: `, bid); return false; } @@ -272,7 +273,7 @@ export const spec = { return extBuilder; }, {}); - const videoParams = BB_HELPERS.transformVideoParams(utils.deepAccess(validBidRequest, 'mediaTypes.video'), utils.deepAccess(validBidRequest, 'params.video')); + const videoParams = BB_HELPERS.transformVideoParams(deepAccess(validBidRequest, 'mediaTypes.video'), deepAccess(validBidRequest, 'params.video')); imps.push({ id: validBidRequest.bidId, ext, secure: window.location.protocol === 'https' ? 1 : 0, video: videoParams }); }); @@ -293,16 +294,16 @@ export const spec = { if (bidderRequest.gdprConsent) { let gdprApplies = 0; if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); - utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(request, 'regs.ext.gdpr', gdprApplies); + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } if (bidderRequest.uspConsent) { - utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); this.syncStore.uspConsent = bidderRequest.uspConsent; } - if (getConfig('coppa') == true) utils.deepSetValue(request, 'regs.coppa', 1); + if (getConfig('coppa') == true) deepSetValue(request, 'regs.coppa', 1); // Enrich the request with any external data we may have BB_HELPERS.addSiteAppDevice(request, bidderRequest.refererInfo && bidderRequest.refererInfo.referer); diff --git a/modules/boldwinBidAdapter.js b/modules/boldwinBidAdapter.js index 04f4085ba24..fcff7134a92 100644 --- a/modules/boldwinBidAdapter.js +++ b/modules/boldwinBidAdapter.js @@ -1,10 +1,10 @@ +import { isFn, deepAccess, logMessage } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; const BIDDER_CODE = 'boldwin'; -const AD_URL = 'https://ssp.videowalldirect.com/?c=o&m=multi'; -const SYNC_URL = 'https://cs.videowalldirect.com/?c=o&m=cookie' +const AD_URL = 'https://ssp.videowalldirect.com/pbjs'; +const SYNC_URL = 'https://cs.videowalldirect.com' function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -23,12 +23,29 @@ function isBidResponseValid(bid) { } } +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + return Boolean(bid.bidId && bid.params && bid.params.placementId); }, buildRequests: (validBidRequests = [], bidderRequest) => { @@ -39,7 +56,7 @@ export const spec = { winTop = window.top; } catch (e) { location = winTop.location; - utils.logMessage(e); + logMessage(e); }; let placements = []; let request = { @@ -63,22 +80,44 @@ export const spec = { for (let i = 0; i < len; i++) { let bid = validBidRequests[i]; - let sizes - if (bid.mediaTypes) { - if (bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { - sizes = bid.mediaTypes[BANNER].sizes - } else if (bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { - sizes = bid.mediaTypes[VIDEO].playerSize + const { mediaTypes } = bid; + const placement = {}; + let sizes; + if (mediaTypes) { + if (mediaTypes[BANNER] && mediaTypes[BANNER].sizes) { + placement.adFormat = BANNER; + sizes = mediaTypes[BANNER].sizes + } else if (mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize) { + placement.adFormat = VIDEO; + sizes = mediaTypes[VIDEO].playerSize + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else { + placement.adFormat = NATIVE; + placement.native = mediaTypes[NATIVE]; } } placements.push({ + ...placement, placementId: bid.params.placementId, bidId: bid.bidId, sizes: sizes || [], wPlayer: sizes ? sizes[0] : 0, hPlayer: sizes ? sizes[1] : 0, - traffic: bid.params.traffic || BANNER, - schain: bid.schain || {} + schain: bid.schain || {}, + bidFloor: getBidFloor(bid), }); } return { @@ -93,6 +132,9 @@ export const spec = { for (let i = 0; i < serverResponse.body.length; i++) { let resItem = serverResponse.body[i]; if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + response.push(resItem); } } diff --git a/modules/boldwinBidAdapter.md b/modules/boldwinBidAdapter.md index 4bf272c4de3..5e2a5b139b3 100644 --- a/modules/boldwinBidAdapter.md +++ b/modules/boldwinBidAdapter.md @@ -24,8 +24,7 @@ Module that connects to boldwin demand sources { bidder: 'boldwin', params: { - placementId: 0, - traffic: 'banner' + placementId: 'testBanner', } } ] @@ -43,8 +42,7 @@ Module that connects to boldwin demand sources { bidder: 'boldwin', params: { - placementId: 0, - traffic: 'video' + placementId: 'testVideo', } } ] diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js new file mode 100644 index 00000000000..60d3c98f15e --- /dev/null +++ b/modules/brandmetricsRtdProvider.js @@ -0,0 +1,168 @@ +/** + * This module adds brandmetrics provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will load load the brandmetrics script and set survey- targeting to ad units of specific bidders. + * @module modules/brandmetricsRtdProvider + * @requires module:modules/realTimeData + */ +import { config } from '../src/config.js' +import { submodule } from '../src/hook.js' +import { deepSetValue, mergeDeep, logError, deepAccess } from '../src/utils.js' +import {loadExternalScript} from '../src/adloader.js' +const MODULE_NAME = 'brandmetrics' +const MODULE_CODE = MODULE_NAME +const RECEIVED_EVENTS = [] +const GVL_ID = 422 +const TCF_PURPOSES = [1, 7] + +function init (config, userConsent) { + const hasConsent = checkConsent(userConsent) + + if (hasConsent) { + const moduleConfig = getMergedConfig(config) + initializeBrandmetrics(moduleConfig.params.scriptId) + } + return hasConsent +} + +/** + * Checks TCF and USP consents + * @param {Object} userConsent + * @returns {boolean} + */ +function checkConsent (userConsent) { + let consent = false + + if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) { + const gdpr = userConsent.gdpr + + if (gdpr.vendorData) { + const vendor = gdpr.vendorData.vendor + const purpose = gdpr.vendorData.purpose + + let vendorConsent = false + if (vendor.consents) { + vendorConsent = vendor.consents[GVL_ID] + } + + if (vendor.legitimateInterests) { + vendorConsent = vendorConsent || vendor.legitimateInterests[GVL_ID] + } + + const purposes = TCF_PURPOSES.map(id => { + return (purpose.consents && purpose.consents[id]) || (purpose.legitimateInterests && purpose.legitimateInterests[id]) + }) + const purposesValid = purposes.filter(p => p === true).length === TCF_PURPOSES.length + consent = vendorConsent && purposesValid + } + } else if (userConsent.usp) { + const usp = userConsent.usp + consent = usp[1] !== 'N' && usp[2] !== 'Y' + } + + return consent +} + +/** +* Add event- listeners to hook in to brandmetrics events +* @param {Object} reqBidsConfigObj +* @param {function} callback +*/ +function processBrandmetricsEvents (reqBidsConfigObj, moduleConfig, callback) { + const callBidTargeting = (event) => { + if (event.available && event.conf) { + const targetingConf = event.conf.displayOption || {} + if (targetingConf.type === 'pbjs') { + setBidderTargeting(reqBidsConfigObj, moduleConfig, targetingConf.targetKey || 'brandmetrics_survey', event.survey.measurementId) + } + } + callback() + } + + if (RECEIVED_EVENTS.length > 0) { + callBidTargeting(RECEIVED_EVENTS[RECEIVED_EVENTS.length - 1]) + } else { + window._brandmetrics = window._brandmetrics || [] + window._brandmetrics.push({ + cmd: '_addeventlistener', + val: { + event: 'surveyloaded', + reEmitLast: true, + handler: (ev) => { + RECEIVED_EVENTS.push(ev) + if (RECEIVED_EVENTS.length === 1) { + // Call bid targeting only for the first received event, if called subsequently, last event from the RECEIVED_EVENTS array is used + callBidTargeting(ev) + } + }, + } + }) + } +} + +/** + * Sets bid targeting of specific bidders + * @param {Object} reqBidsConfigObj + * @param {string} key Targeting key + * @param {string} val Targeting value + */ +function setBidderTargeting (reqBidsConfigObj, moduleConfig, key, val) { + const bidders = deepAccess(moduleConfig, 'params.bidders') + if (bidders && bidders.length > 0) { + const ortb2 = {} + deepSetValue(ortb2, 'ortb2.user.ext.data.' + key, val) + config.setBidderConfig({ + bidders: bidders, + config: ortb2 + }) + } +} + +/** + * Add the brandmetrics script to the page. + * @param {string} scriptId - The script- id provided by brandmetrics or brandmetrics partner + */ +function initializeBrandmetrics(scriptId) { + if (scriptId) { + const path = 'https://cdn.brandmetrics.com/survey/script/' + const file = scriptId + '.js' + const url = path + file + + loadExternalScript(url, MODULE_CODE) + } +} + +/** + * Merges a provided config with default values + * @param {Object} customConfig + * @returns + */ +function getMergedConfig(customConfig) { + return mergeDeep({ + waitForIt: false, + params: { + bidders: [], + scriptId: undefined, + } + }, customConfig) +} + +/** @type {RtdSubmodule} */ +export const brandmetricsSubmodule = { + name: MODULE_NAME, + getBidRequestData: function (reqBidsConfigObj, callback, customConfig) { + try { + const moduleConfig = getMergedConfig(customConfig) + if (moduleConfig.waitForIt) { + processBrandmetricsEvents(reqBidsConfigObj, moduleConfig, callback) + } else { + callback() + } + } catch (e) { + logError(e) + } + }, + init: init +} + +submodule('realTimeData', brandmetricsSubmodule) diff --git a/modules/brandmetricsRtdProvider.md b/modules/brandmetricsRtdProvider.md new file mode 100644 index 00000000000..89ee6bb75cf --- /dev/null +++ b/modules/brandmetricsRtdProvider.md @@ -0,0 +1,40 @@ +# Brandmetrics Real-time Data Submodule +This module is intended to be used by brandmetrics (https://brandmetrics.com) partners and sets targeting keywords to bids if the browser is eligeble to see a brandmetrics survey. +The module hooks in to brandmetrics events and requires a brandmetrics script to be running. The module can optionally load and initialize brandmetrics by providing the 'scriptId'- parameter. + +## Usage +Compile the Brandmetrics RTD module into your Prebid build: +``` +gulp build --modules=rtdModule,brandmetricsRtdProvider +``` + +> Note that the global RTD module, `rtdModule`, is a prerequisite of the Brandmetrics RTD module. + +Enable the Brandmetrics RTD in your Prebid configuration, using the below format: + +```javascript +pbjs.setConfig({ + ..., + realTimeData: { + auctionDelay: 500, // auction delay + dataProviders: [{ + name: 'brandmetrics', + waitForIt: true // should be true if there's an `auctionDelay`, + params: { + scriptId: '00000000-0000-0000-0000-000000000000', + bidders: ['ozone'] + } + }] + }, + ... +}) +``` + +## Parameters +| Name | Type | Description | Default | +| ----------------- | -------------------- | ------------------ | ------------------ | +| name | String | This should always be `brandmetrics` | - | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (recommended) | `false` | +| params | Object | | - | +| params.bidders | String[] | An array of bidders which should receive targeting keys. | `[]` | +| params.scriptId | String | A script- id GUID if the brandmetrics- script should be initialized. | `undefined` | diff --git a/modules/braveBidAdapter.js b/modules/braveBidAdapter.js new file mode 100644 index 00000000000..18bad6b0f75 --- /dev/null +++ b/modules/braveBidAdapter.js @@ -0,0 +1,267 @@ +import { parseUrl, isEmpty, isStr, triggerPixel } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'brave'; +const DEFAULT_CUR = 'USD'; +const ENDPOINT_URL = `https://point.bravegroup.tv/?t=2&partner=hash`; + +const NATIVE_ASSETS_IDS = { 1: 'title', 2: 'icon', 3: 'image', 4: 'body', 5: 'sponsoredBy', 6: 'cta' }; +const NATIVE_ASSETS = { + title: { id: 1, name: 'title' }, + icon: { id: 2, type: 1, name: 'img' }, + image: { id: 3, type: 3, name: 'img' }, + body: { id: 4, type: 2, name: 'data' }, + sponsoredBy: { id: 5, type: 1, name: 'data' }, + cta: { id: 6, type: 12, name: 'data' } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + return !!(bid.params.placementId && bid.params.placementId.toString().length === 32); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests.length === 0 || !bidderRequest) return []; + + const endpointURL = ENDPOINT_URL.replace('hash', validBidRequests[0].params.placementId); + + let imp = validBidRequests.map(br => { + let impObject = { + id: br.bidId, + secure: 1 + }; + + if (br.mediaTypes.banner) { + impObject.banner = createBannerRequest(br); + } else if (br.mediaTypes.video) { + impObject.video = createVideoRequest(br); + } else if (br.mediaTypes.native) { + impObject.native = { + id: br.transactionId, + ver: '1.2', + request: createNativeRequest(br) + }; + } + return impObject; + }); + + let w = window; + let l = w.document.location.href; + let r = w.document.referrer; + + let loopChecker = 0; + while (w !== w.parent) { + if (++loopChecker == 10) break; + try { + w = w.parent; + l = w.location.href; + r = w.document.referrer; + } catch (e) { + break; + } + } + + let page = l || bidderRequest.refererInfo.referer; + + let data = { + id: bidderRequest.bidderRequestId, + cur: [ DEFAULT_CUR ], + device: { + w: screen.width, + h: screen.height, + language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', + ua: navigator.userAgent, + }, + site: { + domain: parseUrl(page).hostname, + page: page, + }, + tmax: bidderRequest.timeout || config.getConfig('bidderTimeout') || 500, + imp + }; + + if (r) { + data.site.ref = r; + } + + if (bidderRequest.gdprConsent) { + data['regs'] = {'ext': {'gdpr': bidderRequest.gdprConsent.gdprApplies ? 1 : 0}}; + data['user'] = {'ext': {'consent': bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : ''}}; + } + + if (bidderRequest.uspConsent !== undefined) { + if (!data['regs'])data['regs'] = {'ext': {}}; + data['regs']['ext']['us_privacy'] = bidderRequest.uspConsent; + } + + if (config.getConfig('coppa') === true) { + if (!data['regs'])data['regs'] = {'coppa': 1}; + else data['regs']['coppa'] = 1; + } + + if (validBidRequests[0].schain) { + data['source'] = {'ext': {'schain': validBidRequests[0].schain}}; + } + + return { + method: 'POST', + url: endpointURL, + data: data + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse) => { + if (!serverResponse || isEmpty(serverResponse.body)) return []; + + let bids = []; + serverResponse.body.seatbid.forEach(response => { + response.bid.forEach(bid => { + let mediaType = bid.ext && bid.ext.mediaType ? bid.ext.mediaType : 'banner'; + + let bidObj = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + ttl: 1200, + currency: DEFAULT_CUR, + netRevenue: true, + creativeId: bid.crid, + dealId: bid.dealid || null, + mediaType: mediaType + }; + + switch (mediaType) { + case 'video': + bidObj.vastUrl = bid.adm; + break; + case 'native': + bidObj.native = parseNative(bid.adm); + break; + default: + bidObj.ad = bid.adm; + } + + bids.push(bidObj); + }); + }); + + return bids; + }, + + onBidWon: (bid) => { + if (isStr(bid.nurl) && bid.nurl !== '') { + triggerPixel(bid.nurl); + } + } +}; + +const parseNative = adm => { + let bid = { + clickUrl: adm.native.link && adm.native.link.url, + impressionTrackers: adm.native.imptrackers || [], + clickTrackers: (adm.native.link && adm.native.link.clicktrackers) || [], + jstracker: adm.native.jstracker || [] + }; + adm.native.assets.forEach(asset => { + let kind = NATIVE_ASSETS_IDS[asset.id]; + let content = kind && asset[NATIVE_ASSETS[kind].name]; + if (content) { + bid[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + + return bid; +} + +const createNativeRequest = br => { + let impObject = { + ver: '1.2', + assets: [] + }; + + let keys = Object.keys(br.mediaTypes.native); + + for (let key of keys) { + const props = NATIVE_ASSETS[key]; + if (props) { + const asset = { + required: br.mediaTypes.native[key].required ? 1 : 0, + id: props.id, + [props.name]: {} + }; + + if (props.type) asset[props.name]['type'] = props.type; + if (br.mediaTypes.native[key].len) asset[props.name]['len'] = br.mediaTypes.native[key].len; + if (br.mediaTypes.native[key].sizes && br.mediaTypes.native[key].sizes[0]) { + asset[props.name]['w'] = br.mediaTypes.native[key].sizes[0]; + asset[props.name]['h'] = br.mediaTypes.native[key].sizes[1]; + } + + impObject.assets.push(asset); + } + } + + return impObject; +} + +const createBannerRequest = br => { + let size = []; + + if (br.mediaTypes.banner.sizes && Array.isArray(br.mediaTypes.banner.sizes)) { + if (Array.isArray(br.mediaTypes.banner.sizes[0])) { size = br.mediaTypes.banner.sizes[0]; } else { size = br.mediaTypes.banner.sizes; } + } else size = [300, 250]; + + return { id: br.transactionId, w: size[0], h: size[1] }; +}; + +const createVideoRequest = br => { + let videoObj = {id: br.transactionId}; + let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'minbitrate', 'maxbitrate', 'api', 'linearity']; + + for (let param of supportParamsList) { + if (br.mediaTypes.video[param] !== undefined) { + videoObj[param] = br.mediaTypes.video[param]; + } + } + + if (br.mediaTypes.video.playerSize && Array.isArray(br.mediaTypes.video.playerSize)) { + if (Array.isArray(br.mediaTypes.video.playerSize[0])) { + videoObj.w = br.mediaTypes.video.playerSize[0][0]; + videoObj.h = br.mediaTypes.video.playerSize[0][1]; + } else { + videoObj.w = br.mediaTypes.video.playerSize[0]; + videoObj.h = br.mediaTypes.video.playerSize[1]; + } + } else { + videoObj.w = 640; + videoObj.h = 480; + } + + return videoObj; +} + +registerBidder(spec); diff --git a/modules/braveBidAdapter.md b/modules/braveBidAdapter.md new file mode 100644 index 00000000000..77d2338ff16 --- /dev/null +++ b/modules/braveBidAdapter.md @@ -0,0 +1,135 @@ +# Overview + +``` +Module Name: Brave Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@thebrave.io +``` + +# Description + +Module which connects to Brave SSP demand sources + +# Test Parameters + + +250x300 banner test +``` +var adUnits = [{ + code: 'brave-prebid', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'brave', + params : { + placementId : "to0QI2aPgkbBZq6vgf0oHitouZduz0qw" // test placementId, please replace after test + } + }] +}]; +``` + +native test +``` +var adUnits = [{ + code: 'brave-native-prebid', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bids: [{ + bidder: 'brave', + params: { + placementId : "to0QI2aPgkbBZq6vgf0oHitouZduz0qw" // test placementId, please replace after test + } + }] +}]; +``` + +video test +``` +var adUnits = [{ + code: 'brave-video-prebid', + mediaTypes: { + video: { + minduration:1, + maxduration:999, + boxingallowed:1, + skip:0, + mimes:[ + 'application/javascript', + 'video/mp4' + ], + playerSize: [[768, 1024]], + protocols:[ + 2,3 + ], + linearity:1, + api:[ + 1, + 2 + ] + } + }, + bids: [{ + bidder: 'brave', + params: { + placementId : "to0QI2aPgkbBZq6vgf0oHitouZduz0qw" // test placementId, please replace after test + } + }] +}]; +``` + +# Bid Parameters +## Banner + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `placementId` | required | String | The placement ID from Brave | "to0QI2aPgkbBZq6vgf0oHitouZduz0qw" + + +# Ad Unit and page Setup: + +```html + + + +``` diff --git a/modules/bridgewellBidAdapter.js b/modules/bridgewellBidAdapter.js index 58c4e907328..5d545b6f722 100644 --- a/modules/bridgewellBidAdapter.js +++ b/modules/bridgewellBidAdapter.js @@ -1,11 +1,11 @@ -import * as utils from '../src/utils.js'; +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'; const BIDDER_CODE = 'bridgewell'; -const REQUEST_ENDPOINT = 'https://prebid.scupio.com/recweb/prebid.aspx?cb=' + Math.random(); -const BIDDER_VERSION = '0.0.3'; +const REQUEST_ENDPOINT = 'https://prebid.scupio.com/recweb/prebid.aspx?cb='; +const BIDDER_VERSION = '1.1.0'; export const spec = { code: BIDDER_CODE, @@ -37,7 +37,12 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { const adUnits = []; - utils._each(validBidRequests, function (bid) { + var bidderUrl = REQUEST_ENDPOINT + Math.random(); + var userIds; + + _each(validBidRequests, function (bid) { + userIds = bid.userId; + if (bid.params.cid) { adUnits.push({ cid: bid.params.cid, @@ -47,7 +52,8 @@ export const spec = { banner: { sizes: bid.sizes } - } + }, + userIds: userIds || {} }); } else { adUnits.push({ @@ -58,7 +64,8 @@ export const spec = { banner: { sizes: bid.sizes } - } + }, + userIds: userIds || {} }); } }); @@ -70,13 +77,13 @@ export const spec = { return { method: 'POST', - url: REQUEST_ENDPOINT, + url: bidderUrl, data: { version: { prebid: '$prebid.version$', bridgewell: BIDDER_VERSION }, - inIframe: utils.inIframe(), + inIframe: inIframe(), url: topUrl, referrer: getTopWindowReferrer(), adUnits: adUnits, @@ -97,7 +104,7 @@ export const spec = { const bidResponses = []; // map responses to requests - utils._each(bidRequest.validBidRequests, function (req) { + _each(bidRequest.validBidRequests, function (req) { const bidResponse = {}; if (!serverResponse.body) { @@ -161,7 +168,7 @@ export const spec = { bidResponse.mediaType = matchedResponse.mediaType; if (matchedResponse.adomain) { - utils.deepSetValue(bidResponse, 'meta.advertiserDomains', Array.isArray(matchedResponse.adomain) ? matchedResponse.adomain : [matchedResponse.adomain]); + deepSetValue(bidResponse, 'meta.advertiserDomains', Array.isArray(matchedResponse.adomain) ? matchedResponse.adomain : [matchedResponse.adomain]); } // check required parameters by matchedResponse.mediaType diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js index 5539004bdcd..d3ae1d9cf43 100644 --- a/modules/brightMountainMediaBidAdapter.js +++ b/modules/brightMountainMediaBidAdapter.js @@ -1,22 +1,12 @@ +import { generateUUID, deepAccess, logWarn, deepSetValue } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'bmtm'; -const AD_URL = 'https://one.elitebidder.com/api/hb'; +const AD_URL = 'https://one.elitebidder.com/api/hb?sid='; const SYNC_URL = 'https://console.brightmountainmedia.com:8443/cookieSync'; - -const videoExt = [ - 'video/x-ms-wmv', - 'video/x-flv', - 'video/mp4', - 'video/3gpp', - 'application/x-mpegURL', - 'video/quicktime', - 'video/x-msvideo', - 'application/x-shockwave-flash', - 'application/javascript' -]; +const CURRENCY = 'USD'; export const spec = { code: BIDDER_CODE, @@ -24,94 +14,124 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placement_id); + if (bid.bidId && bid.bidder && bid.params && bid.params.placement_id) { + return true; + } + if (bid.params.placement_id == 0 && bid.params.test === 1) { + return true; + } + return false; }, buildRequests: (validBidRequests, bidderRequest) => { - let winTop = window; - let location; - try { - location = new URL(bidderRequest.refererInfo.referer) - winTop = window.top; - } catch (e) { - location = winTop.location; - utils.logMessage(e); - }; - let placements = []; - let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements + let requestData = []; + let size = [0, 0]; + let oRTBRequest = { + at: 2, + site: buildSite(bidderRequest), + device: buildDevice(), + cur: [CURRENCY], + tmax: 1000, + regs: buildRegs(bidderRequest), + user: {}, + source: {}, }; - if (bidderRequest) { - if (bidderRequest.gdprConsent) { - request.gdpr_consent = bidderRequest.gdprConsent.consentString || 'ALL' - request.gdpr_require = bidderRequest.gdprConsent.gdprApplies ? 1 : 0 - } - } - for (let i = 0; i < validBidRequests.length; i++) { - let bid = validBidRequests[i]; - let placement = { - placementId: bid.params.placement_id, - bidId: bid.bidId, - }; - - if (bid.mediaTypes.hasOwnProperty(BANNER)) { - placement['traffic'] = BANNER; + + validBidRequests.forEach((bid) => { + oRTBRequest['id'] = generateUUID(); + oRTBRequest['imp'] = [ + { + id: '1', + bidfloor: 0, + bidfloorcur: CURRENCY, + secure: document.location.protocol === 'https:' ? 1 : 0, + ext: { + placement_id: bid.params.placement_id, + prebidVersion: '$prebid.version$', + } + }, + ]; + + if (deepAccess(bid, 'mediaTypes.banner')) { if (bid.mediaTypes.banner.sizes) { - placement['sizes'] = bid.mediaTypes.banner.sizes; + size = bid.mediaTypes.banner.sizes[0]; } - } - if (bid.mediaTypes.hasOwnProperty(VIDEO)) { - placement['traffic'] = VIDEO; - if (bid.mediaTypes.video.context) { - placement['context'] = bid.mediaTypes.video.context; + oRTBRequest.imp[0].banner = { + h: size[0], + w: size[1], } + } else { if (bid.mediaTypes.video.playerSize) { - placement['sizes'] = bid.mediaTypes.video.playerSize; - } - if (bid.mediaTypes.video.mimes && Array.isArray(bid.mediaTypes.video.mimes)) { - placement['mimes'] = bid.mediaTypes.video.mimes; - } else { - placement['mimes'] = videoExt; + size = bid.mediaTypes.video.playerSize[0]; } - if (bid.mediaTypes.video.skip != undefined) { - placement['skip'] = bid.mediaTypes.video.skip; - } - if (bid.mediaTypes.video.playbackmethod && Array.isArray(bid.mediaTypes.video.playbackmethod)) { - placement['playbackmethod'] = bid.mediaTypes.video.playbackmethod; + + oRTBRequest.imp[0].video = { + h: size[0], + w: size[1], + mimes: bid.mediaTypes.video.mimes ? bid.mediaTypes.video.mimes : [], + skip: bid.mediaTypes.video.skip ? 1 : 0, + playbackmethod: bid.mediaTypes.video.playbackmethod ? bid.mediaTypes.video.playbackmethod : [], + protocols: bid.mediaTypes.video.protocols ? bid.mediaTypes.video.protocols : [], + api: bid.mediaTypes.video.api ? bid.mediaTypes.video.api : [], + minduration: bid.mediaTypes.video.minduration ? bid.mediaTypes.video.minduration : 1, + maxduration: bid.mediaTypes.video.maxduration ? bid.mediaTypes.video.maxduration : 999, } } - if (bid.schain) { - placement.schain = bid.schain; - } - placements.push(placement); - } - return { - method: 'POST', - url: AD_URL, - data: request - }; + + oRTBRequest.imp[0].bidfloor = getFloor(bid, size); + oRTBRequest.user = getUserIdAsEids(bid.userIdAsEids) + oRTBRequest.source = getSchain(bid.schain) + + requestData.push({ + method: 'POST', + url: `${AD_URL}${bid.params.placement_id}`, + data: JSON.stringify(oRTBRequest), + bidRequest: bid, + }) + }); + return requestData; }, - interpretResponse: (serverResponse) => { + interpretResponse: (serverResponse, { bidRequest }) => { let bidResponse = []; - const response = serverResponse.body; - if (response && Array.isArray(response) && response.length > 0) { - for (let i = 0; i < response.length; i++) { - if (response[i].cpm > 0) { - if (response[i].mediaType && response[i].mediaType === 'video') { - response[i].vastXml = response[i].ad; - } - bidResponse.push(response[i]); - } + let bid; + let response; + + try { + response = serverResponse.body + bid = response.seatbid[0].bid[0]; + } catch (e) { + response = null; + } + + if (!response || !bid || !bid.adm || !bid.price) { + logWarn(`Bidder ${spec.code} no valid bid`); + return []; + } + + let tempResponse = { + requestId: bidRequest.bidId, + cpm: bid.price, + currency: response.cur, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + mediaType: deepAccess(bidRequest, 'mediaTypes.banner') ? BANNER : VIDEO, + ttl: 3000, + netRevenue: true, + meta: { + advertiserDomains: bid.adomain } + }; + + if (tempResponse.mediaType === BANNER) { + tempResponse.ad = replaceAuctionPrice(bid.adm, bid.price); + } else { + tempResponse.vastXml = replaceAuctionPrice(bid.adm, bid.price); } + + bidResponse.push(tempResponse); return bidResponse; }, @@ -127,3 +147,107 @@ export const spec = { }; registerBidder(spec); + +function buildSite(bidderRequest) { + let site = { + name: window.location.hostname, + publisher: { + domain: window.location.hostname, + } + }; + + if (bidderRequest && bidderRequest.refererInfo) { + deepSetValue( + site, + 'page', + bidderRequest.refererInfo.referer.href ? bidderRequest.refererInfo.referer.href : '', + ); + deepSetValue( + site, + 'ref', + bidderRequest.refererInfo.referer ? bidderRequest.refererInfo.referer : '', + ); + } + return site; +} + +function buildDevice() { + return { + ua: navigator.userAgent, + w: window.top.screen.width, + h: window.top.screen.height, + js: 1, + language: navigator.language, + dnt: navigator.doNotTrack === 'yes' || navigator.doNotTrack == '1' || + navigator.msDoNotTrack == '1' ? 1 : 0, + } +} + +function buildRegs(bidderRequest) { + let regs = { + coppa: config.getConfig('coppa') == true ? 1 : 0, + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + deepSetValue( + regs, + 'ext.gdpr', + bidderRequest.gdprConsent.gdprApplies ? 1 : 0, + ); + deepSetValue( + regs, + 'ext.gdprConsentString', + bidderRequest.gdprConsent.consentString || 'ALL', + ); + } + + if (bidderRequest && bidderRequest.uspConsent) { + deepSetValue(regs, + 'ext.us_privacy', + bidderRequest.uspConsent); + } + return regs; +} + +function replaceAuctionPrice(str, cpm) { + if (!str) return; + return str.replace(/\$\{AUCTION_PRICE\}/g, cpm); +} + +function getFloor(bid, size) { + if (typeof bid.getFloor === 'function') { + let floorInfo = {}; + floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: 'banner', + size: size, + }); + + if (typeof floorInfo === 'object' && floorInfo.currency === 'USD') { + return parseFloat(floorInfo.floor); + } + } + return 0; +} + +function getUserIdAsEids(userIds) { + if (userIds) { + return { + ext: { + eids: userIds, + } + } + }; + return {}; +} + +function getSchain(schain) { + if (schain) { + return { + ext: { + schain: schain, + } + } + } + return {}; +} diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index 2aad211b186..4895f303973 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { getBidIdParameter, _each, isArray, getWindowTop, getUniqueIdentifierStr, parseUrl, deepSetValue, logError, logWarn, createTrackPixelHtml, getWindowSelf, isFn, isPlainObject } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -22,17 +22,17 @@ function buildRequests(bidReqs, bidderRequest) { referrer = bidderRequest.refererInfo.referer; } const brightcomImps = []; - const publisherId = utils.getBidIdParameter('publisherId', bidReqs[0].params); - utils._each(bidReqs, function (bid) { + const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); + _each(bidReqs, function (bid) { let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; - bidSizes = ((utils.isArray(bidSizes) && utils.isArray(bidSizes[0])) ? bidSizes : [bidSizes]); - bidSizes = bidSizes.filter(size => utils.isArray(size)); + bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => isArray(size)); const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); const element = document.getElementById(bid.adUnitCode); const minSize = _getMinSize(processedSizes); const viewabilityAmount = _isViewabilityMeasurable(element) - ? _getViewability(element, utils.getWindowTop(), minSize) + ? _getViewability(element, getWindowTop(), minSize) : 'na'; const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); @@ -46,17 +46,17 @@ function buildRequests(bidReqs, bidderRequest) { }, tagid: String(bid.adUnitCode) }; - const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); + const bidFloor = _getBidFloor(bid); if (bidFloor) { imp.bidfloor = bidFloor; } brightcomImps.push(imp); }); const brightcomBidReq = { - id: utils.getUniqueIdentifierStr(), + id: getUniqueIdentifierStr(), imp: brightcomImps, site: { - domain: utils.parseUrl(referrer).host, + domain: parseUrl(referrer).host, page: referrer, publisher: { id: publisherId @@ -71,8 +71,8 @@ function buildRequests(bidReqs, bidderRequest) { }; if (bidderRequest && bidderRequest.gdprConsent) { - utils.deepSetValue(brightcomBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); - utils.deepSetValue(brightcomBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(brightcomBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + deepSetValue(brightcomBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } return { @@ -82,7 +82,7 @@ function buildRequests(bidReqs, bidderRequest) { options: {contentType: 'text/plain', withCredentials: false} }; } catch (e) { - utils.logError(e, {bidReqs, bidderRequest}); + logError(e, {bidReqs, bidderRequest}); } } @@ -100,7 +100,7 @@ function isBidRequestValid(bid) { function interpretResponse(serverResponse) { if (!serverResponse.body || typeof serverResponse.body != 'object') { - utils.logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); return []; } const { body: {id, seatbid} } = serverResponse; @@ -122,13 +122,16 @@ function interpretResponse(serverResponse) { netRevenue: true, mediaType: BANNER, ad: _getAdMarkup(brightcomBid), - ttl: 60 + ttl: 60, + meta: { + advertiserDomains: brightcomBid && brightcomBid.adomain ? brightcomBid.adomain : [] + } }); }); } return brightcomBidResponses; } catch (e) { - utils.logError(e, {id, seatbid}); + logError(e, {id, seatbid}); } } @@ -152,7 +155,7 @@ function _getDeviceType() { function _getAdMarkup(bid) { let adm = bid.adm; if ('nurl' in bid) { - adm += utils.createTrackPixelHtml(bid.nurl); + adm += createTrackPixelHtml(bid.nurl); } return adm; } @@ -162,14 +165,14 @@ function _isViewabilityMeasurable(element) { } function _getViewability(element, topWin, { w, h } = {}) { - return utils.getWindowTop().document.visibilityState === 'visible' + return getWindowTop().document.visibilityState === 'visible' ? _getPercentInView(element, topWin, { w, h }) : 0; } function _isIframe() { try { - return utils.getWindowSelf() !== utils.getWindowTop(); + return getWindowSelf() !== getWindowTop(); } catch (e) { return true; } @@ -248,4 +251,20 @@ function _getPercentInView(element, topWin, { w, h } = {}) { return 0; } +function _getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + registerBidder(spec); diff --git a/modules/britepoolIdSystem.js b/modules/britepoolIdSystem.js index 3bf416957d2..2316fbb732d 100644 --- a/modules/britepoolIdSystem.js +++ b/modules/britepoolIdSystem.js @@ -5,7 +5,7 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js' +import { isEmpty, triggerPixel, logError } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; const PIXEL = 'https://px.britepool.com/new?partner_id=t'; @@ -47,14 +47,14 @@ export const britepoolIdSubmodule = { }; } } - if (utils.isEmpty(params)) { - utils.triggerPixel(PIXEL); + if (isEmpty(params)) { + triggerPixel(PIXEL); } // Return for async operation return { callback: function(callback) { if (errors.length > 0) { - errors.forEach(error => utils.logError(error)); + errors.forEach(error => logError(error)); callback(); return; } @@ -65,7 +65,7 @@ export const britepoolIdSubmodule = { callback(britepoolIdSubmodule.normalizeValue(response)); }); } catch (error) { - if (error !== '') utils.logError(error); + if (error !== '') logError(error); callback(); } } else { @@ -75,7 +75,7 @@ export const britepoolIdSubmodule = { callback(responseObj ? { primaryBPID: responseObj.primaryBPID } : null); }, error: error => { - if (error !== '') utils.logError(error); + if (error !== '') logError(error); callback(); } }, JSON.stringify(params), { customHeaders: headers, contentType: 'application/json', method: 'POST', withCredentials: true }); @@ -132,7 +132,7 @@ export const britepoolIdSubmodule = { try { valueObj = JSON.parse(value); } catch (error) { - utils.logError(error); + logError(error); } } return valueObj; diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 4ee338e94cc..a1943afda8d 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -15,21 +15,27 @@ * @property {?string} keyName */ -import * as utils from '../src/utils.js'; +import { deepClone, logError, isGptPubadsDefined, isNumber, isFn, deepSetValue } from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {ajaxBuilder} from '../src/ajax.js'; import {loadExternalScript} from '../src/adloader.js'; import {getStorageManager} from '../src/storageManager.js'; import find from 'core-js-pure/features/array/find.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import includes from 'core-js-pure/features/array/includes.js'; const storage = getStorageManager(); /** @type {ModuleParams} */ let _moduleParams = {}; /** @type {null|Object} */ -let _predictionsData = null; +let _browsiData = null; /** @type {string} */ const DEF_KEYNAME = 'browsiViewability'; +/** @type {null | function} */ +let _dataReadyCallback = null; +/** @type {null|Object} */ +let _ic = {}; /** * add browsi script to page @@ -43,7 +49,7 @@ export function addBrowsiTag(data) { script.setAttribute('prebidbpt', 'true'); script.setAttribute('id', 'browsi-tag'); script.setAttribute('src', data.u); - script.prebidData = utils.deepClone(data); + script.prebidData = deepClone(data); if (_moduleParams.keyName) { script.prebidData.kn = _moduleParams.keyName; } @@ -61,7 +67,7 @@ export function collectData() { try { browsiData = storage.getDataFromLocalStorage('__brtd'); } catch (e) { - utils.logError('unable to parse __brtd'); + logError('unable to parse __brtd'); } let predictorData = { @@ -78,29 +84,49 @@ export function collectData() { getPredictionsFromServer(`//${_moduleParams.url}/prebid?${toUrlParams(predictorData)}`); } +/** + * wait for data from server + * call callback when data is ready + * @param {function} callback + */ +function waitForData(callback) { + if (_browsiData) { + _dataReadyCallback = null; + callback(_browsiData); + } else { + _dataReadyCallback = callback; + } +} + export function setData(data) { - _predictionsData = data; + _browsiData = data; + if (isFn(_dataReadyCallback)) { + _dataReadyCallback(_browsiData); + _dataReadyCallback = null; + } } -function sendDataToModule(adUnitsCodes) { +function getRTD(auc) { try { - const _predictions = (_predictionsData && _predictionsData.p) || {}; - return adUnitsCodes.reduce((rp, adUnitCode) => { - if (!adUnitCode) { + const _bp = (_browsiData && _browsiData.p) || {}; + return auc.reduce((rp, uc) => { + _ic[uc] = _ic[uc] || 0; + const _c = _ic[uc]; + if (!uc) { return rp } - const adSlot = getSlotByCode(adUnitCode); - const identifier = adSlot ? getMacroId(_predictionsData['pmd'], adSlot) : adUnitCode; - const predictionData = _predictions[identifier]; - rp[adUnitCode] = getKVObject(-1, _predictionsData['kn']); - if (!predictionData) { + const adSlot = getSlotByCode(uc); + const identifier = adSlot ? getMacroId(_browsiData['pmd'], adSlot) : uc; + const _pd = _bp[identifier]; + rp[uc] = getKVObject(-1); + if (!_pd) { return rp } - if (predictionData.p) { - if (!isIdMatchingAdUnit(adSlot, predictionData.w)) { + if (_pd.ps) { + if (!isIdMatchingAdUnit(adSlot, _pd.w)) { return rp; } - rp[adUnitCode] = getKVObject(predictionData.p, _predictionsData.kn); + rp[uc] = getKVObject(getCurrentData(_pd.ps, _c)); } return rp; }, {}); @@ -109,12 +135,37 @@ function sendDataToModule(adUnitsCodes) { } } +/** + * get prediction + * return -1 if prediction not found + * @param {object} predictionObject + * @param {number} _c + * @return {number} + */ +export function getCurrentData(predictionObject, _c) { + if (!predictionObject || !isNumber(_c)) { + return -1; + } + if (isNumber(predictionObject[_c])) { + return predictionObject[_c]; + } + if (Object.keys(predictionObject).length > 1) { + while (_c > 0) { + _c--; + if (isNumber(predictionObject[_c])) { + return predictionObject[_c]; + } + } + } + return -1; +} + /** * get all slots on page * @return {Object[]} slot GoogleTag slots */ function getAllSlots() { - return utils.isGptPubadsDefined() && window.googletag.pubads().getSlots(); + return isGptPubadsDefined() && window.googletag.pubads().getSlots(); } /** * get prediction and return valid object for key value set @@ -122,12 +173,16 @@ function getAllSlots() { * @param {string?} keyName * @return {Object} key:value */ -function getKVObject(p, keyName) { +function getKVObject(p) { const prValue = p < 0 ? 'NA' : (Math.floor(p * 10) / 10).toFixed(2); let prObject = {}; - prObject[((_moduleParams['keyName'] || keyName || DEF_KEYNAME).toString())] = prValue.toString(); + prObject[getKey()] = prValue.toString(); return prObject; } + +function getKey() { + return ((_moduleParams['keyName'] || (_browsiData && _browsiData['kn']) || DEF_KEYNAME).toString()) +} /** * check if placement id matches one of given ad units * @param {Object} slot google slot @@ -169,7 +224,7 @@ export function getMacroId(macro, slot) { }); return macroResult; } catch (e) { - utils.logError(`failed to evaluate: ${macro}`); + logError(`failed to evaluate: ${macro}`); } } return slot.getSlotElementId(); @@ -211,7 +266,7 @@ function getPredictionsFromServer(url) { } addBrowsiTag(data); } catch (err) { - utils.logError('unable to parse data'); + logError('unable to parse data'); setData({}) } } else if (req.status === 204) { @@ -221,7 +276,7 @@ function getPredictionsFromServer(url) { }, error: function () { setData({}); - utils.logError('unable to get prediction data'); + logError('unable to get prediction data'); } } ); @@ -238,6 +293,28 @@ function toUrlParams(data) { .join('&'); } +function setBidRequestsData(bidObj, callback) { + let adUnitCodes = bidObj.adUnitCodes; + let adUnits = bidObj.adUnits || getGlobal().adUnits || []; + if (adUnitCodes) { + adUnits = adUnits.filter(au => includes(adUnitCodes, au.code)); + } else { + adUnitCodes = adUnits.map(au => au.code); + } + waitForData(() => { + const data = getRTD(adUnitCodes); + if (data) { + adUnits.forEach(adUnit => { + const adUnitCode = adUnit.code; + if (data[adUnitCode]) { + deepSetValue(adUnit, 'ortb2Imp.ext.data.browsi', {[getKey()]: data[adUnitCode][getKey()]}); + } + }); + } + callback(); + }) +} + /** @type {RtdSubmodule} */ export const browsiSubmodule = { /** @@ -250,16 +327,27 @@ export const browsiSubmodule = { * @function * @param {string[]} adUnitsCodes */ - getTargetingData: sendDataToModule, + getTargetingData: getTargetingData, init: init, + getBidRequestData: setBidRequestsData }; +function getTargetingData(uc) { + const targetingData = getRTD(uc); + uc.forEach(auc => { + if (isNumber(_ic[auc])) { + _ic[auc] = _ic[auc] + 1; + } + }); + return targetingData; +} + function init(moduleConfig) { _moduleParams = moduleConfig.params; if (_moduleParams && _moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { collectData(); } else { - utils.logError('missing params for Browsi provider'); + logError('missing params for Browsi provider'); } return true; } diff --git a/modules/bucksenseBidAdapter.js b/modules/bucksenseBidAdapter.js index 3f327e62121..fcf99179993 100644 --- a/modules/bucksenseBidAdapter.js +++ b/modules/bucksenseBidAdapter.js @@ -1,6 +1,6 @@ +import { logInfo } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; const WHO = 'BKSHBID-005'; const BIDDER_CODE = 'bucksense'; @@ -17,7 +17,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - utils.logInfo(WHO + ' isBidRequestValid() - INPUT bid:', bid); + logInfo(WHO + ' isBidRequestValid() - INPUT bid:', bid); if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { return false; } @@ -34,7 +34,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - utils.logInfo(WHO + ' buildRequests() - INPUT validBidRequests:', validBidRequests, 'INPUT bidderRequest:', bidderRequest); + logInfo(WHO + ' buildRequests() - INPUT validBidRequests:', validBidRequests, 'INPUT bidderRequest:', bidderRequest); let requests = []; const len = validBidRequests.length; for (let i = 0; i < len; i++) { @@ -64,7 +64,7 @@ export const spec = { data: sendData }); } - utils.logInfo(WHO + ' buildRequests() - requests:', requests); + logInfo(WHO + ' buildRequests() - requests:', requests); return requests; }, @@ -75,7 +75,7 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function (serverResponse, request) { - utils.logInfo(WHO + ' interpretResponse() - INPUT serverResponse:', serverResponse, 'INPUT request:', request); + logInfo(WHO + ' interpretResponse() - INPUT serverResponse:', serverResponse, 'INPUT request:', request); const bidResponses = []; if (serverResponse.body) { @@ -90,14 +90,15 @@ export const spec = { var sCurrency = oResponse.currency || 'USD'; var bNetRevenue = oResponse.netRevenue || true; var sAd = oResponse.ad || ''; + var sAdomains = oResponse.adomains || []; if (request && sRequestID.length == 0) { - utils.logInfo(WHO + ' interpretResponse() - use RequestID from Placments'); + logInfo(WHO + ' interpretResponse() - use RequestID from Placments'); sRequestID = request.data.bid_id || ''; } if (request && request.data.params.hasOwnProperty('testcpm')) { - utils.logInfo(WHO + ' interpretResponse() - use Test CPM '); + logInfo(WHO + ' interpretResponse() - use Test CPM '); nCPM = request.data.params.testcpm; } @@ -110,13 +111,16 @@ export const spec = { creativeId: sCreativeID, currency: sCurrency, netRevenue: bNetRevenue, - ad: sAd + ad: sAd, + meta: { + advertiserDomains: sAdomains + } }; bidResponses.push(bidResponse); } else { - utils.logInfo(WHO + ' interpretResponse() - serverResponse not valid'); + logInfo(WHO + ' interpretResponse() - serverResponse not valid'); } - utils.logInfo(WHO + ' interpretResponse() - return', bidResponses); + logInfo(WHO + ' interpretResponse() - return', bidResponses); return bidResponses; }, diff --git a/modules/buzzoolaBidAdapter.js b/modules/buzzoolaBidAdapter.js index f87607657c3..c6e27c94e04 100644 --- a/modules/buzzoolaBidAdapter.js +++ b/modules/buzzoolaBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from '../src/utils.js'; +import { deepAccess, deepClone } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {OUTSTREAM} from '../src/video.js'; @@ -11,7 +11,7 @@ const RENDERER_SRC = 'https://tube.buzzoola.com/new/build/buzzlibrary.js'; export const spec = { code: BIDDER_CODE, aliases: ['buzzoolaAdapter'], - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. @@ -21,7 +21,7 @@ export const spec = { */ isBidRequestValid: function (bid) { let types = bid.mediaTypes; - return !!(bid && bid.mediaTypes && (types.banner || types.video) && bid.params && bid.params.placementId); + return !!(bid && bid.mediaTypes && (types.banner || types.video || types.native) && bid.params && bid.params.placementId); }, /** @@ -62,8 +62,8 @@ export const spec = { return response.map(bid => { let requestBid = requestBids[bid.requestId]; - let context = utils.deepAccess(requestBid, 'mediaTypes.video.context'); - let validBid = utils.deepClone(bid); + let context = deepAccess(requestBid, 'mediaTypes.video.context'); + let validBid = deepClone(bid); if (validBid.mediaType === VIDEO && context === OUTSTREAM) { let renderer = Renderer.install({ @@ -88,7 +88,7 @@ export const spec = { */ function setOutstreamRenderer(bid) { let adData = JSON.parse(bid.ad); - let unitSettings = utils.deepAccess(adData, 'placement.unit_settings'); + let unitSettings = deepAccess(adData, 'placement.unit_settings'); let extendedSettings = { width: '' + bid.width, height: '' + bid.height, diff --git a/modules/buzzoolaBidAdapter.md b/modules/buzzoolaBidAdapter.md index aec3eda6c58..87f5262af4f 100644 --- a/modules/buzzoolaBidAdapter.md +++ b/modules/buzzoolaBidAdapter.md @@ -26,7 +26,7 @@ var adUnits = [ bids: [{ bidder: 'buzzoola', params: { - placementId: 417846 + placementId: 417845 } }] }, @@ -45,7 +45,7 @@ var adUnits = [ bids: [{ bidder: 'buzzoola', params: { - placementId: 417845 + placementId: 417846 } }] }, @@ -67,6 +67,44 @@ var adUnits = [ placementId: 417845 } }] + }, + // Native adUnit + { + code: '/21737252144/prebid_test_native', + mediaTypes: { + native: { + image: { + required: true, + sizes: [640, 134] + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bids: [{ + bidder: 'buzzoola', + params: { + placementId: 417845 + } + }] } ]; ``` diff --git a/modules/byDataAnalyticsAdapter.js b/modules/byDataAnalyticsAdapter.js new file mode 100644 index 00000000000..ef6e1a503ee --- /dev/null +++ b/modules/byDataAnalyticsAdapter.js @@ -0,0 +1,311 @@ +import { deepClone, logInfo, logError } from '../src/utils.js'; +import Base64 from 'crypto-js/enc-base64'; +import hmacSHA512 from 'crypto-js/hmac-sha512'; +import enc from 'crypto-js/enc-utf8'; +import adapter from '../src/AnalyticsAdapter.js'; +import CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager.js'; +import { ajax } from '../src/ajax.js'; + +const secretKey = 'bydata@123456'; +const { EVENTS: { NO_BID, BID_TIMEOUT, AUCTION_END } } = CONSTANTS; +const DEFAULT_EVENT_URL = 'https://pbjs-stream.bydata.com/topics/prebid'; +const analyticsType = 'endpoint'; +var payload = {}; +var bdNbTo = { 'to': [], 'nb': [] }; +let initOptions = {}; + +function onBidTimeout(t) { + if (payload['visitor_data'] && t && t.length > 0) { + bdNbTo['to'] = t; + } +} + +function onNoBidData(t) { + if (payload['visitor_data'] && t) { + bdNbTo['nb'].push(t); + } +} + +function onAuctionEnd(t) { + _logInfo('onAuctionEnd', t); + const {isCorrectOption, logFrequency} = initOptions; + var value = Math.floor(Math.random() * 10000 + 1); + _logInfo(' value - frequency ', (value + '-' + logFrequency)); + setTimeout(() => { + if (isCorrectOption && value < logFrequency) { + ascAdapter.dataProcess(t); + addKeyForPrebidWinningAndWinningsBids(); + ascAdapter.sendPayload(); + } + }, 500); +} + +const ascAdapter = Object.assign(adapter({ url: DEFAULT_EVENT_URL, analyticsType: analyticsType }), { + track({ eventType, args }) { + switch (eventType) { + case NO_BID: + onNoBidData(args); + break; + case BID_TIMEOUT: + onBidTimeout(args); + break; + case AUCTION_END: + onAuctionEnd(args); + break; + default: + break; + } + } +}); + +// save the base class function +ascAdapter.originEnableAnalytics = ascAdapter.enableAnalytics; +// override enableAnalytics so we can get access to the config passed in from the page +ascAdapter.enableAnalytics = function(config) { + if (this.initConfig(config)) { + _logInfo('initiated:', initOptions); + initOptions.isCorrectOption && ascAdapter.getVisitorData(); + ascAdapter.originEnableAnalytics(config); + } +}; + +ascAdapter.initConfig = function (config) { + let isCorrectOption = true; + initOptions = {}; + _logInfo('initConfig', config); + initOptions.options = deepClone(config.options); + initOptions.clientId = initOptions.options.clientId || null; + initOptions.logFrequency = initOptions.options.logFrequency; + if (!initOptions.clientId) { + _logError('"options.clientId" should not empty!!'); + isCorrectOption = false; + } + initOptions.isCorrectOption = isCorrectOption; + this.initOptions = initOptions; + return isCorrectOption; +}; + +ascAdapter.getVisitorData = function(data = {}) { + var ua = data.userId ? data : {}; + var module = { + options: [], + header: [window.navigator.platform, window.navigator.userAgent, window.navigator.appVersion, window.navigator.vendor, window.opera], + dataos: [ + { name: 'Windows Phone', value: 'Windows Phone', version: 'OS' }, + { name: 'Windows', value: 'Win', version: 'NT' }, + { name: 'iPhone', value: 'iPhone', version: 'OS' }, + { name: 'iPad', value: 'iPad', version: 'OS' }, + { name: 'Kindle', value: 'Silk', version: 'Silk' }, + { name: 'Android', value: 'Android', version: 'Android' }, + { name: 'PlayBook', value: 'PlayBook', version: 'OS' }, + { name: 'BlackBerry', value: 'BlackBerry', version: '/' }, + { name: 'Macintosh', value: 'Mac', version: 'OS X' }, + { name: 'Linux', value: 'Linux', version: 'rv' }, + { name: 'Palm', value: 'Palm', version: 'PalmOS' } + ], + databrowser: [ + { name: 'Chrome', value: 'Chrome', version: 'Chrome' }, + { name: 'Firefox', value: 'Firefox', version: 'Firefox' }, + { name: 'Safari', value: 'Safari', version: 'Version' }, + { name: 'Internet Explorer', value: 'MSIE', version: 'MSIE' }, + { name: 'Opera', value: 'Opera', version: 'Opera' }, + { name: 'BlackBerry', value: 'CLDC', version: 'CLDC' }, + { name: 'Mozilla', value: 'Mozilla', version: 'Mozilla' } + ], + init: function () { var agent = this.header.join(' '); var os = this.matchItem(agent, this.dataos); var browser = this.matchItem(agent, this.databrowser); return { os: os, browser: browser }; }, + matchItem: function (string, data) { + var i = 0; var j = 0; var regex; var regexv; var match; var matches; var version; + for (i = 0; i < data.length; i += 1) { + regex = new RegExp(data[i].value, 'i'); + match = regex.test(string); + if (match) { + regexv = new RegExp(data[i].version + '[- /:;]([\\d._]+)', 'i'); + matches = string.match(regexv); + version = ''; + if (matches) { if (matches[1]) { matches = matches[1]; } } + if (matches) { + matches = matches.split(/[._]+/); + for (j = 0; j < matches.length; j += 1) { + if (j === 0) { + version += matches[j] + '.'; + } else { + version += matches[j]; + } + } + } else { + version = '0'; + } + return { + name: data[i].name, + version: parseFloat(version) + }; + } + } + return { name: 'unknown', version: 0 }; + } + }; + + function generateUid() { + try { + var buffer = new Uint8Array(16); + crypto.getRandomValues(buffer); + buffer[6] = (buffer[6] & ~176) | 64; + buffer[8] = (buffer[8] & ~64) | 128; + var hex = Array.prototype.map.call(new Uint8Array(buffer), function(x) { + return ('00' + x.toString(16)).slice(-2); + }).join(''); + return hex.slice(0, 5) + '-' + hex.slice(5, 9) + '-' + hex.slice(9, 13) + '-' + hex.slice(13, 18); + } catch (e) { + return ''; + } + } + function base64url(source) { + var encodedSource = Base64.stringify(source); + encodedSource = encodedSource.replace(/=+$/, ''); + encodedSource = encodedSource.replace(/\+/g, '-'); + encodedSource = encodedSource.replace(/\//g, '_'); + return encodedSource; + } + function getJWToken(data) { + var header = { + 'alg': 'HS256', + 'typ': 'JWT' + }; + var stringifiedHeader = enc.parse(JSON.stringify(header)); + var encodedHeader = base64url(stringifiedHeader); + var stringifiedData = enc.parse(JSON.stringify(data)); + var encodedData = base64url(stringifiedData); + var token = encodedHeader + '.' + encodedData; + var signature = hmacSHA512(token, secretKey); + signature = base64url(signature); + var signedToken = token + '.' + signature; + return signedToken; + } + const {clientId} = initOptions; + var userId = window.localStorage.getItem('userId'); + if (!userId) { + userId = generateUid(); + window.localStorage.setItem('userId', userId); + } + var screenSize = {width: window.screen.width, height: window.screen.height}; + var deviceType = window.navigator.userAgent.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i) ? 'Mobile' : 'Desktop'; + var e = module.init(); + if (!ua['userId']) { + ua['userId'] = userId; + ua['client_id'] = clientId; + ua['plateform_name'] = e.os.name; + ua['os_version'] = e.os.version; + ua['browser_name'] = e.browser.name; + ua['browser_version'] = e.browser.version; + ua['screen_size'] = screenSize; + ua['device_type'] = deviceType; + ua['time_zone'] = window.Intl.DateTimeFormat().resolvedOptions().timeZone; + } + var signedToken = getJWToken(ua); + payload['visitor_data'] = signedToken; + return signedToken; +} + +ascAdapter.dataProcess = function(t) { + payload['auction_id'] = t.auctionId; + payload['auction_start'] = t.timestamp; + payload['auctionData'] = []; + var bidderRequestsData = []; var bidsReceivedData = []; + t.bidderRequests && t.bidderRequests.forEach(bidReq => { + var pObj = {}; pObj['bids'] = []; + bidReq.bids.forEach(bid => { + var data = {}; + data['adUnitCode'] = bid.adUnitCode; + data['sizes'] = bid.sizes; + data['bidder'] = bid.bidder; + data['bidId'] = bid.bidId; + data['mediaTypes'] = []; + var mt = bid.mediaTypes.banner ? 'display' : 'video'; + data['mediaTypes'].push(mt); + pObj['bids'].push(data); + }) + bidderRequestsData.push(pObj); + }); + t.bidsReceived && t.bidsReceived.forEach(bid => { + const {requestId, bidder, width, height, cpm, currency, timeToRespond, adUnitCode} = bid; + bidsReceivedData.push({requestId, bidder, width, height, cpm, currency, timeToRespond, adUnitCode}); + }); + bidderRequestsData.length > 0 && bidderRequestsData.forEach(bdObj => { + var bdsArray = bdObj['bids']; + bdsArray.forEach(bid => { + const {adUnitCode, sizes, bidder, bidId, mediaTypes} = bid; + sizes.forEach(size => { + var sstr = size[0] + 'x' + size[1] + payload['auctionData'].push({adUnit: adUnitCode, size: sstr, media_type: mediaTypes[0], bids_bidder: bidder, bids_bid_id: bidId}); + }); + }); + }); + bidsReceivedData.length > 0 && bidsReceivedData.forEach(bdRecived => { + const {requestId, bidder, width, height, cpm, currency, timeToRespond} = bdRecived; + payload['auctionData'].forEach(rwData => { + if (rwData['bids_bid_id'] === requestId && rwData['size'] === width + 'x' + height) { + rwData['br_request_id'] = requestId; rwData['br_bidder'] = bidder; rwData['br_pb_mg'] = cpm; + rwData['br_currency'] = currency; rwData['br_time_to_respond'] = timeToRespond; rwData['br_size'] = width + 'x' + height; + } + }) + }); + payload['auctionData'] && payload['auctionData'].length > 0 && payload['auctionData'].forEach(u => { + bdNbTo['to'].forEach(i => { + if (u.bids_bid_id === i.bidId) u.is_timeout = 1; + }); + bdNbTo['nb'].forEach(i => { + if (u.adUnit === i.adUnitCode && u.bids_bidder === i.bidder && u.bids_bid_id === i.bidId) { u.is_nobid = 1; } + }) + }); + return payload; +} + +ascAdapter.sendPayload = function () { + var obj = { 'records': [ { 'value': payload } ] }; + let strJSON = JSON.stringify(obj); + _logInfo(' sendPayload ', JSON.stringify(obj)); + ajax(DEFAULT_EVENT_URL, undefined, strJSON, { + contentType: 'application/vnd.kafka.json.v2+json', + method: 'POST', + withCredentials: true + }); +} + +function addKeyForPrebidWinningAndWinningsBids() { + var prebidWinningBids = $$PREBID_GLOBAL$$.getAllPrebidWinningBids(); + var winningBids = $$PREBID_GLOBAL$$.getAllWinningBids(); + prebidWinningBids && prebidWinningBids.length > 0 && prebidWinningBids.forEach(pbbid => { + payload['auctionData'] && payload['auctionData'].forEach(rwData => { + if (rwData['bids_bid_id'] === pbbid.requestId && rwData['br_size'] === pbbid.size) { + rwData['is_prebid_winning_bid'] = 1; + } + }); + }) + winningBids && winningBids.length > 0 && winningBids.forEach(wBid => { + payload['auctionData'] && payload['auctionData'].forEach(rwData => { + if (rwData['bids_bid_id'] === wBid.requestId && rwData['br_size'] === wBid.size) { + rwData['is_winning_bid'] = 1; + } + }); + }) +} + +adapterManager.registerAnalyticsAdapter({ + adapter: ascAdapter, + code: 'bydata' +}); + +function _logInfo(message, meta) { + logInfo(buildLogMessage(message), meta); +} + +function _logError(message) { + logError(buildLogMessage(message)); +} + +function buildLogMessage(message) { + return 'Bydata Prebid Analytics: ' + message; +} + +export default ascAdapter; diff --git a/modules/byDataAnalyticsAdapter.md b/modules/byDataAnalyticsAdapter.md new file mode 100644 index 00000000000..84207d8b3a1 --- /dev/null +++ b/modules/byDataAnalyticsAdapter.md @@ -0,0 +1,34 @@ +# Overview + +layout: Analytics Adapter +title: Ascendeum Pvt Ltd. (https://ascendeum.com/) +description: Bydata Analytics Adapter +modulecode: byDataAnalyticsAdapter +gdpr_supported: false (EU GDPR support) +usp_supported: false (US Privacy support) +coppa_supported: false (COPPA support) +prebid_member: false +gvl_id: (IAB Global Vendor List ID) +enable_download: false (in case you don't want users of the website to download your adapter) + +Module Name: Bydata Analytics Adapter +Module Type: Analytics Adapter +Maintainer: Ascendeum + +# Description + +Analytics adapter for https://ascendeum.com/. Contact engineering@ascendeum.com for information. + +# Test Parameters + +``` +{ + provider: 'bydata', + options : { + clientId: "ASCENDEUM_PROVIDED_CLIENT_ID", + logFrequency : 100, // Sample Rate Default - 1% + } +} +``` + + diff --git a/modules/byplayBidAdapter.js b/modules/byplayBidAdapter.js deleted file mode 100644 index 6133cdfa647..00000000000 --- a/modules/byplayBidAdapter.js +++ /dev/null @@ -1,67 +0,0 @@ -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; -import { VIDEO } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'byplay'; -const ENDPOINT_URL = 'https://prebid.byplay.net/bidder'; -const VIDEO_PLAYER_URL = 'https://cdn.byplay.net/prebid-byplay-v2.js'; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [VIDEO], - isBidRequestValid: (bid) => { - return !!bid.params.sectionId; - }, - buildRequests: function(validBidRequests) { - return validBidRequests.map(req => { - const payload = { - requestId: req.bidId, - sectionId: req.params.sectionId, - ...(req.params.env ? { env: req.params.env } : {}) - }; - - return { - method: 'POST', - url: ENDPOINT_URL, - data: JSON.stringify(payload), - options: { - withCredentials: false - } - }; - }); - }, - interpretResponse: (serverResponse, bidderRequest) => { - const response = serverResponse.body; - const data = JSON.parse(bidderRequest.data); - const bidResponse = { - requestId: data.requestId, - cpm: response.cpm, - width: response.width, - height: response.height, - creativeId: response.creativeId || '0', - ttl: config.getConfig('_bidderTimeout'), - currency: 'JPY', - netRevenue: response.netRevenue, - mediaType: VIDEO, - vastXml: response.vastXml, - renderer: createRenderer() - }; - - return [bidResponse]; - } -}; - -function createRenderer() { - const renderer = Renderer.install({ url: VIDEO_PLAYER_URL }); - - renderer.setRender(bid => { - bid.renderer.push(() => { - window.adtagRender(bid); - }); - }); - - return renderer; -} - -registerBidder(spec); diff --git a/modules/c1xBidAdapter.js b/modules/c1xBidAdapter.js deleted file mode 100644 index 8e1f1487ba7..00000000000 --- a/modules/c1xBidAdapter.js +++ /dev/null @@ -1,178 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; -import { userSync } from '../src/userSync.js'; - -const BIDDER_CODE = 'c1x'; -const URL = 'https://ht.c1exchange.com/ht'; -const PIXEL_ENDPOINT = 'https://px.c1exchange.com/pubpixel/'; -const LOG_MSG = { - invalidBid: 'C1X: [ERROR] bidder returns an invalid bid', - noSite: 'C1X: [ERROR] no site id supplied', - noBid: 'C1X: [INFO] creating a NO bid for Adunit: ', - bidWin: 'C1X: [INFO] creating a bid for Adunit: ' -}; - -/** - * Adapter for requesting bids from C1X header tag server. - * v3.1 (c) C1X Inc., 2018 - */ - -export const c1xAdapter = { - code: BIDDER_CODE, - - // check the bids sent to c1x bidder - isBidRequestValid: function(bid) { - const siteId = bid.params.siteId || ''; - if (!siteId) { - utils.logError(LOG_MSG.noSite); - } - return !!(bid.adUnitCode && siteId); - }, - - buildRequests: function(bidRequests, bidderRequest) { - let payload = {}; - let tagObj = {}; - let pixelUrl = ''; - const adunits = bidRequests.length; - const rnd = new Date().getTime(); - const c1xTags = bidRequests.map(bidToTag); - const bidIdTags = bidRequests.map(bidToShortTag); // include only adUnitCode and bidId from request obj - - // flattened tags in a tag object - tagObj = c1xTags.reduce((current, next) => Object.assign(current, next)); - const pixelId = tagObj.pixelId; - - payload = { - adunits: adunits.toString(), - rnd: rnd.toString(), - response: 'json', - compress: 'gzip' - } - - // for GDPR support - if (bidderRequest && bidderRequest.gdprConsent) { - payload['consent_string'] = bidderRequest.gdprConsent.consentString; - payload['consent_required'] = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies.toString() : 'true' - ; - } - - if (pixelId) { - pixelUrl = PIXEL_ENDPOINT + pixelId; - if (payload.consent_required) { - pixelUrl += '&gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? 1 : 0); - pixelUrl += '&consent=' + encodeURIComponent(bidderRequest.gdprConsent.consentString || ''); - } - userSync.registerSync('image', BIDDER_CODE, pixelUrl); - } - - Object.assign(payload, tagObj); - - let payloadString = stringifyPayload(payload); - // ServerRequest object - return { - method: 'GET', - url: URL, - data: payloadString, - bids: bidIdTags - }; - }, - - interpretResponse: function(serverResponse, requests) { - serverResponse = serverResponse.body; - requests = requests.bids || []; - const currency = 'USD'; - const bidResponses = []; - let netRevenue = false; - - if (!serverResponse || serverResponse.error) { - let errorMessage = serverResponse.error; - utils.logError(LOG_MSG.invalidBid + errorMessage); - return bidResponses; - } else { - serverResponse.forEach(bid => { - if (bid.bid) { - if (bid.bidType === 'NET_BID') { - netRevenue = !netRevenue; - } - const curBid = { - width: bid.width, - height: bid.height, - cpm: bid.cpm, - ad: bid.ad, - creativeId: bid.crid, - currency: currency, - ttl: 300, - netRevenue: netRevenue - }; - - for (let i = 0; i < requests.length; i++) { - if (bid.adId === requests[i].adUnitCode) { - curBid.requestId = requests[i].bidId; - } - } - utils.logInfo(LOG_MSG.bidWin + bid.adId + ' size: ' + curBid.width + 'x' + curBid.height); - bidResponses.push(curBid); - } else { - // no bid - utils.logInfo(LOG_MSG.noBid + bid.adId); - } - }); - } - - return bidResponses; - } -} - -function bidToTag(bid, index) { - const tag = {}; - const adIndex = 'a' + (index + 1).toString(); // ad unit id for c1x - const sizeKey = adIndex + 's'; - const priceKey = adIndex + 'p'; - // TODO: Multiple Floor Prices - - const sizesArr = bid.sizes; - const floorPriceMap = bid.params.floorPriceMap || ''; - tag['site'] = bid.params.siteId || ''; - - // prevent pixelId becoming undefined when publishers don't fill this param in ad units they have on the same page - if (bid.params.pixelId) { - tag['pixelId'] = bid.params.pixelId - } - - tag[adIndex] = bid.adUnitCode; - tag[sizeKey] = sizesArr.reduce((prev, current) => prev + (prev === '' ? '' : ',') + current.join('x'), ''); - - const newSizeArr = tag[sizeKey].split(','); - if (floorPriceMap) { - newSizeArr.forEach(size => { - if (size in floorPriceMap) { - tag[priceKey] = floorPriceMap[size].toString(); - } // we only accept one cpm price in floorPriceMap - }); - } - if (bid.params.pageurl) { - tag['pageurl'] = bid.params.pageurl; - } - - return tag; -} - -function bidToShortTag(bid) { - const tag = {}; - tag.adUnitCode = bid.adUnitCode; - tag.bidId = bid.bidId; - - return tag; -} - -function stringifyPayload(payload) { - let payloadString = ''; - payloadString = JSON.stringify(payload).replace(/":"|","|{"|"}/g, (foundChar) => { - if (foundChar == '":"') return '='; - else if (foundChar == '","') return '&'; - else return ''; - }); - return payloadString; -} - -registerBidder(c1xAdapter); diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index 37b6fdc3e98..38bc99f1d83 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js' +import { deepAccess, isArray, _each, logWarn, isEmpty } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import { config } from '../src/config.js' import { getStorageManager } from '../src/storageManager.js'; @@ -20,7 +20,7 @@ function _getDeviceObj () { function _getSiteObj (bidderRequest) { let site = {} - let url = config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); + let url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); if (url.length > 0) { url = url.split('?')[0] } @@ -30,19 +30,19 @@ function _getSiteObj (bidderRequest) { } function _validateSizes (sizeObj, type) { - if (!utils.isArray(sizeObj) || typeof sizeObj[0] === 'undefined') { + if (!isArray(sizeObj) || typeof sizeObj[0] === 'undefined') { return false } - if (type === 'video' && (!utils.isArray(sizeObj[0]) || sizeObj[0].length !== 2)) { + if (type === 'video' && (!isArray(sizeObj[0]) || sizeObj[0].length !== 2)) { return false } let result = true if (type === 'banner') { - utils._each(sizeObj, function (size) { - if (!utils.isArray(size) || (size.length !== 2)) { + _each(sizeObj, function (size) { + if (!isArray(size) || (size.length !== 2)) { result = false } }) @@ -50,11 +50,11 @@ function _validateSizes (sizeObj, type) { } if (type === 'old') { - if (!utils.isArray(sizeObj[0]) && sizeObj.length !== 2) { + if (!isArray(sizeObj[0]) && sizeObj.length !== 2) { result = false - } else if (utils.isArray(sizeObj[0])) { - utils._each(sizeObj, function (size) { - if (!utils.isArray(size) || (size.length !== 2)) { + } else if (isArray(sizeObj[0])) { + _each(sizeObj, function (size) { + if (!isArray(size) || (size.length !== 2)) { result = false } }) @@ -70,22 +70,22 @@ function _buildBid (bid) { placement.id = bid.bidId placement.secure = 1 - let sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes') || utils.deepAccess(bid, 'mediaTypes.video.playerSize') || utils.deepAccess(bid, 'sizes') + let sizes = deepAccess(bid, 'mediaTypes.banner.sizes') || deepAccess(bid, 'mediaTypes.video.playerSize') || deepAccess(bid, 'sizes') - if (utils.deepAccess(bid, 'mediaTypes.banner') || utils.deepAccess(bid, 'mediaType') === 'banner' || (!utils.deepAccess(bid, 'mediaTypes.video') && !utils.deepAccess(bid, 'mediaType'))) { + if (deepAccess(bid, 'mediaTypes.banner') || deepAccess(bid, 'mediaType') === 'banner' || (!deepAccess(bid, 'mediaTypes.video') && !deepAccess(bid, 'mediaType'))) { placement.banner = {'format': []} - if (utils.isArray(sizes[0])) { - utils._each(sizes, function (size) { + if (isArray(sizes[0])) { + _each(sizes, function (size) { placement.banner.format.push({'w': size[0], 'h': size[1]}) }) } else { placement.banner.format.push({'w': sizes[0], 'h': sizes[1]}) } - } else if (utils.deepAccess(bid, 'mediaTypes.video') || utils.deepAccess(bid, 'mediaType') === 'video') { + } else if (deepAccess(bid, 'mediaTypes.video') || deepAccess(bid, 'mediaType') === 'video') { placement.video = {} if (typeof sizes !== 'undefined') { - if (utils.isArray(sizes[0])) { + if (isArray(sizes[0])) { placement.video.w = sizes[0][0] placement.video.h = sizes[0][1] } else { @@ -94,12 +94,12 @@ function _buildBid (bid) { } } - placement.video.protocols = utils.deepAccess(bid, 'params.video.protocols') || SUPPORTED_VIDEO_PROTOCOLS - placement.video.mimes = utils.deepAccess(bid, 'params.video.mimes') || SUPPORTED_VIDEO_MIMES - placement.video.playbackmethod = utils.deepAccess(bid, 'params.video.playbackmethod') || SUPPORTED_VIDEO_PLAYBACK_METHODS - placement.video.skip = utils.deepAccess(bid, 'params.video.skip') || 0 - if (placement.video.skip === 1 && utils.deepAccess(bid, 'params.video.skipafter')) { - placement.video.skipafter = utils.deepAccess(bid, 'params.video.skipafter') + placement.video.protocols = deepAccess(bid, 'mediaTypes.video.protocols') || deepAccess(bid, 'params.video.protocols') || SUPPORTED_VIDEO_PROTOCOLS + placement.video.mimes = deepAccess(bid, 'mediaTypes.video.mimes') || deepAccess(bid, 'params.video.mimes') || SUPPORTED_VIDEO_MIMES + placement.video.playbackmethod = deepAccess(bid, 'mediaTypes.video.playbackmethod') || deepAccess(bid, 'params.video.playbackmethod') || SUPPORTED_VIDEO_PLAYBACK_METHODS + placement.video.skip = deepAccess(bid, 'mediaTypes.video.skip') || deepAccess(bid, 'params.video.skip') || 0 + if (placement.video.skip === 1 && (deepAccess(bid, 'mediaTypes.video.skipafter') || deepAccess(bid, 'params.video.skipafter'))) { + placement.video.skipafter = deepAccess(bid, 'mediaTypes.video.skipafter') || deepAccess(bid, 'params.video.skipafter') } } @@ -131,7 +131,7 @@ function _buildResponse (bid, currency, ttl) { resp.ad = bid.adm } - if (utils.deepAccess(bid, 'dealid')) { + if (deepAccess(bid, 'dealid')) { resp.dealId = bid.dealid } @@ -143,30 +143,30 @@ export const spec = { supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { - if (!utils.deepAccess(bid, 'params.placementId')) { - utils.logWarn('placementId param is reqeuired.') + if (!deepAccess(bid, 'params.placementId')) { + logWarn('placementId param is reqeuired.') return false } - if (utils.deepAccess(bid, 'mediaTypes.banner.sizes')) { + if (deepAccess(bid, 'mediaTypes.banner.sizes')) { let isValid = _validateSizes(bid.mediaTypes.banner.sizes, 'banner') if (!isValid) { - utils.logWarn('Bid sizes are invalid.') + logWarn('Bid sizes are invalid.') } return isValid - } else if (utils.deepAccess(bid, 'mediaTypes.video.playerSize')) { + } else if (deepAccess(bid, 'mediaTypes.video.playerSize')) { let isValid = _validateSizes(bid.mediaTypes.video.playerSize, 'video') if (!isValid) { - utils.logWarn('Bid sizes are invalid.') + logWarn('Bid sizes are invalid.') } return isValid - } else if (utils.deepAccess(bid, 'sizes')) { + } else if (deepAccess(bid, 'sizes')) { let isValid = _validateSizes(bid.sizes, 'old') if (!isValid) { - utils.logWarn('Bid sizes are invalid.') + logWarn('Bid sizes are invalid.') } return isValid } else { - utils.logWarn('Bid sizes are required.') + logWarn('Bid sizes are required.') return false } }, @@ -195,7 +195,7 @@ export const spec = { }; } - utils._each(validBidRequests, function (bid) { + _each(validBidRequests, function (bid) { requestBody.imp.push(_buildBid(bid)) }) // Return the server request @@ -210,9 +210,9 @@ export const spec = { const bidResponses = [] // response is not empty (HTTP 204) - if (!utils.isEmpty(serverResponse.body)) { - utils._each(serverResponse.body.seatbid, function (seatbid) { - utils._each(seatbid.bid, function (bid) { + if (!isEmpty(serverResponse.body)) { + _each(serverResponse.body.seatbid, function (seatbid) { + _each(seatbid.bid, function (bid) { bidResponses.push(_buildResponse(bid, serverResponse.body.cur, serverResponse.body.ext.ttl)) }) }) @@ -223,8 +223,8 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { const syncs = [] - if (utils.deepAccess(serverResponses[0], 'body.ext.usersync') && !utils.isEmpty(serverResponses[0].body.ext.usersync)) { - utils._each(serverResponses[0].body.ext.usersync, function (match) { + if (deepAccess(serverResponses[0], 'body.ext.usersync') && !isEmpty(serverResponses[0].body.ext.usersync)) { + _each(serverResponses[0].body.ext.usersync, function (match) { if ((syncOptions.iframeEnabled && match.type === 'iframe') || (syncOptions.pixelEnabled && match.type === 'image')) { syncs.push({ type: match.type, diff --git a/modules/ccxBidAdapter.md b/modules/ccxBidAdapter.md index 740457dd099..7d86507bccb 100644 --- a/modules/ccxBidAdapter.md +++ b/modules/ccxBidAdapter.md @@ -32,67 +32,21 @@ Module that connects to Clickonometrics's demand sources mediaTypes: { video: { playerSize: [1920, 1080] - + protocols: [2, 3, 5, 6], //default + mimes: ["video/mp4", "video/x-flv"], //default + playbackmethod: [1, 2, 3, 4], //default + skip: 1, //default 0 + skipafter: 5 //delete this key if skip = 0 } }, bids: [ { bidder: "ccx", params: { - placementId: 3287742, - //following options are not required, default values will be used. Uncomment if you want to use custom values - /*video: { - //check OpenRTB documentation for following options description - protocols: [2, 3, 5, 6], //default - mimes: ["video/mp4", "video/x-flv"], //default - playbackmethod: [1, 2, 3, 4], //default - skip: 1, //default 0 - skipafter: 5 //delete this key if skip = 0 - }*/ + placementId: 3287742 } } ] } ]; - -# Pre 1.0 Support - - var adUnits = [ - { - code: 'test-banner', - mediaType: 'banner', - sizes: [300, 250], - bids: [ - { - bidder: "ccx", - params: { - placementId: 3286844 - } - } - ] - }, - { - code: 'test-video', - mediaType: 'video', - sizes: [1920, 1080] - bids: [ - { - bidder: "ccx", - params: { - placementId: 3287742, - //following options are not required, default values will be used. Uncomment if you want to use custom values - /*video: { - //check OpenRTB documentation for following options description - protocols: [2, 3, 5, 6], //default - mimes: ["video/mp4", "video/x-flv"], //default - playbackmethod: [1, 2, 3, 4], //default - skip: 1, //default 0 - skipafter: 5 //delete this key if skip = 0 - }*/ - } - } - ] - } - - ]; \ No newline at end of file diff --git a/modules/cedatoBidAdapter.js b/modules/cedatoBidAdapter.js deleted file mode 100644 index ab381698f01..00000000000 --- a/modules/cedatoBidAdapter.js +++ /dev/null @@ -1,229 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const storage = getStorageManager(); - -const BIDDER_CODE = 'cedato'; -const BID_URL = 'https://h.cedatoplayer.com/hb'; -const SYNC_URL = 'https://h.cedatoplayer.com/hb_usync'; -const TTL = 10000; -const CURRENCY = 'USD'; -const NET_REVENUE = true; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], - - isBidRequestValid: function(bid) { - return !!( - bid && - bid.params && - bid.params.player_id && - utils.checkCookieSupport() && - storage.cookiesAreEnabled() - ); - }, - - buildRequests: function(bidRequests, bidderRequest) { - const site = { domain: document.domain }; - const device = { ua: navigator.userAgent, w: screen.width, h: screen.height }; - const currency = CURRENCY; - const tmax = bidderRequest.timeout; - const auctionId = bidderRequest.auctionId; - const auctionStart = bidderRequest.auctionStart; - const bidderRequestId = bidderRequest.bidderRequestId; - - const imp = bidRequests.map(req => { - const banner = getMediaType(req, 'banner'); - const video = getMediaType(req, 'video'); - const params = req.params; - const bidId = req.bidId; - const adUnitCode = req.adUnitCode; - const bidRequestsCount = req.bidRequestsCount; - const bidderWinsCount = req.bidderWinsCount; - const transactionId = req.transactionId; - - return { - bidId, - banner, - video, - adUnitCode, - bidRequestsCount, - bidderWinsCount, - transactionId, - params - }; - }); - - const payload = { - version: '$prebid.version$', - site, - device, - imp, - currency, - tmax, - auctionId, - auctionStart, - bidderRequestId - }; - - if (bidderRequest) { - payload.referer_info = bidderRequest.refererInfo; - payload.us_privacy = bidderRequest.uspConsent; - - if (bidderRequest.gdprConsent) { - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - } - } - - return formatRequest(payload, bidderRequest); - }, - - interpretResponse: function(resp, {bidderRequest}) { - resp = resp.body; - const bids = []; - - if (!resp) { - return bids; - } - - resp.seatbid[0].bid.map(serverBid => { - const bid = newBid(serverBid, bidderRequest); - bid.currency = resp.cur; - bids.push(bid); - }); - - return bids; - }, - - getUserSyncs: function(syncOptions, resps, gdprConsent, uspConsent) { - const syncs = []; - if (syncOptions.iframeEnabled) { - syncs.push(getSync('iframe', gdprConsent, uspConsent)); - } else if (syncOptions.pixelEnabled) { - syncs.push(getSync('image', gdprConsent, uspConsent)); - } - return syncs; - } -} - -function getMediaType(req, type) { - const { mediaTypes } = req; - - if (!mediaTypes) { - return; - } - - switch (type) { - case 'banner': - if (mediaTypes.banner) { - const { sizes } = mediaTypes.banner; - return { - format: getFormats(sizes) - }; - } - break; - - case 'video': - if (mediaTypes.video) { - const { playerSize, context } = mediaTypes.video; - return { - context: context, - format: getFormats(playerSize) - }; - } - } -} - -function newBid(serverBid, bidderRequest) { - const bidRequest = utils.getBidRequest(serverBid.uuid, [bidderRequest]); - - const cpm = serverBid.price; - const requestId = serverBid.uuid; - const width = serverBid.w; - const height = serverBid.h; - const creativeId = serverBid.crid; - const dealId = serverBid.dealid; - const mediaType = serverBid.media_type; - const netRevenue = NET_REVENUE; - const ttl = TTL; - - const bid = { - cpm, - requestId, - width, - height, - mediaType, - creativeId, - dealId, - netRevenue, - ttl, - }; - - if (mediaType == 'video') { - const videoContext = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); - - if (videoContext == 'instream') { - bid.vastUrl = serverBid.vast_url; - bid.vastImpUrl = serverBid.notify_url; - } - } else { - bid.ad = serverBid.adm; - } - - return bid; -} - -function formatRequest(payload, bidderRequest) { - const payloadByUrl = {}; - const requests = []; - - payload.imp.forEach(imp => { - const url = imp.params.bid_url || BID_URL; - if (!payloadByUrl[url]) { - payloadByUrl[url] = { - ...payload, - imp: [] - }; - } - payloadByUrl[url].imp.push(imp); - }); - - for (const url in payloadByUrl) { - requests.push({ - url, - method: 'POST', - data: JSON.stringify(payloadByUrl[url]), - bidderRequest - }); - } - - return requests; -} - -const getSync = (type, gdprConsent, uspConsent = '') => { - const syncUrl = SYNC_URL; - let params = '?type=' + type + '&us_privacy=' + uspConsent; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - params += `&gdpr_consent=${gdprConsent.consentString}`; - } - } - return { - type: type, - url: syncUrl + params, - }; -} - -const getFormats = arr => arr.map((s) => { - return { w: s[0], h: s[1] }; -}); - -registerBidder(spec); diff --git a/modules/cleanioRtdProvider.js b/modules/cleanioRtdProvider.js new file mode 100644 index 00000000000..b9fdcef768e --- /dev/null +++ b/modules/cleanioRtdProvider.js @@ -0,0 +1,192 @@ +/** + * This module adds clean.io provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will wrap bid responses markup in clean.io agent script for protection + * @module modules/cleanioRtdProvider + * @requires module:modules/realTimeData + */ + +import { submodule } from '../src/hook.js'; +import { logError, generateUUID, insertElement } from '../src/utils.js'; + +// ============================ MODULE STATE =============================== + +/** + * @type {function(): void} + * Page-wide initialization step / strategy + */ +let onModuleInit = () => {}; + +/** + * @type {function(Object): void} + * Bid response mutation step / strategy. + */ +let onBidResponse = () => {}; + +/** + * @type {number} + * 0 for unknown, 1 for preloaded, -1 for error. + */ +let preloadStatus = 0; + +// ============================ MODULE LOGIC =============================== + +/** + * Page initialization step which just preloads the script, to be available whenever we start processing the bids. + * @param {string} scriptURL The script URL to preload + */ +function pageInitStepPreloadScript(scriptURL) { + const linkElement = document.createElement('link'); + linkElement.rel = 'preload'; + linkElement.as = 'script'; + linkElement.href = scriptURL; + linkElement.onload = () => { preloadStatus = 1; }; + linkElement.onerror = () => { preloadStatus = -1; }; + insertElement(linkElement); +} + +/** + * Page initialization step which adds the protector script to the whole page. With that, there is no need wrapping bids, and the coverage is better. + * @param {string} scriptURL The script URL to add to the page for protection + */ +function pageInitStepProtectPage(scriptURL) { + const scriptElement = document.createElement('script'); + scriptElement.type = 'text/javascript'; + scriptElement.src = scriptURL; + insertElement(scriptElement); +} + +/** + * Bid processing step which alters the ad HTML to contain bid-specific information, which can be used to identify the creative later. + * @param {Object} bidResponse Bid response data + */ +function bidWrapStepAugmentHtml(bidResponse) { + bidResponse.ad = `\n${bidResponse.ad}`; +} + +/** + * Bid processing step which applies creative protection by wrapping the ad HTML. + * @param {string} scriptURL + * @param {number} requiredPreload + * @param {Object} bidResponse + */ +function bidWrapStepProtectByWrapping(scriptURL, requiredPreload, bidResponse) { + // Still prepend bid info, it's always helpful to have creative data in its payload + bidWrapStepAugmentHtml(bidResponse); + + // If preloading failed, or if configuration requires us to finish preloading - + // we should not process this bid any further + if (preloadStatus < requiredPreload) { + return; + } + + const sid = generateUUID(); + bidResponse.ad = ` + + + `; +} + +/** + * Custom error class to differentiate validation errors + */ +class ConfigError extends Error { } + +/** + * The function to be called upon module init. Depending on the passed config, initializes properly init/bid steps or throws ConfigError. + * @param {Object} config + */ +function readConfig(config) { + if (!config.params) { + throw new ConfigError('Missing config parameters for clean.io RTD module provider.'); + } + + if (typeof config.params.cdnUrl !== 'string' || !/^https?:\/\//.test(config.params.cdnUrl)) { + throw new ConfigError('Parameter "cdnUrl" is a required string parameter, which should start with "http(s)://".'); + } + + if (typeof config.params.protectionMode !== 'string') { + throw new ConfigError('Parameter "protectionMode" is a required string parameter.'); + } + + const scriptURL = config.params.cdnUrl; + + switch (config.params.protectionMode) { + case 'full': + onModuleInit = () => pageInitStepProtectPage(scriptURL); + onBidResponse = (bidResponse) => bidWrapStepAugmentHtml(bidResponse); + break; + + case 'bids': + onModuleInit = () => pageInitStepPreloadScript(scriptURL); + onBidResponse = (bidResponse) => bidWrapStepProtectByWrapping(scriptURL, 0, bidResponse); + break; + + case 'bids-nowait': + onModuleInit = () => pageInitStepPreloadScript(scriptURL); + onBidResponse = (bidResponse) => bidWrapStepProtectByWrapping(scriptURL, 1, bidResponse); + break; + + default: + throw new ConfigError('Parameter "protectionMode" must be one of "full" | "bids" | "bids-nowait".'); + } +} + +// ============================ MODULE REGISTRATION =============================== + +/** + * The function which performs submodule registration. + */ +function beforeInit() { + submodule('realTimeData', /** @type {RtdSubmodule} */ ({ + name: 'clean.io', + + init: (config, userConsent) => { + try { + readConfig(config); + onModuleInit(); + return true; + } catch (err) { + if (err instanceof ConfigError) { + logError(err.message); + } + return false; + } + }, + + onBidResponseEvent: (bidResponse, config, userConsent) => { + onBidResponse(bidResponse); + } + })); +} + +/** + * Exporting local (and otherwise encapsulated to this module) functions + * for testing purposes + */ +export const __TEST__ = { + pageInitStepPreloadScript, + pageInitStepProtectPage, + bidWrapStepAugmentHtml, + bidWrapStepProtectByWrapping, + ConfigError, + readConfig, + beforeInit, +} + +beforeInit(); diff --git a/modules/cleanioRtdProvider.md b/modules/cleanioRtdProvider.md new file mode 100644 index 00000000000..7870a2719b6 --- /dev/null +++ b/modules/cleanioRtdProvider.md @@ -0,0 +1,59 @@ +# Overview + +``` +Module Name: clean.io Rtd provider +Module Type: Rtd Provider +Maintainer: nick@clean.io +``` + +The clean.io Realtime module provides effective anti-malvertising solution for publishers, including, but not limited to, +blocking unwanted 0- and 1-click redirects, deceptive ads or those with malicious landing pages, and various types of affiliate fraud. + +Using this module requires prior agreement with [clean.io](https://clean.io) to obtain the necessary distribution key. + + +# Integration + +clean.io Realtime module can be built just like any other prebid module: + +``` +gulp build --modules=cleanioRtdProvider,... +``` + + +# Configuration + +When built into prebid.js, this module can be configured through the following `pbjs.setConfig` call: + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'clean.io', + params: { + cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', ///< Contact clean.io to get your own CDN URL + protectionMode: 'full', ///< Supported modes are 'full', 'bids' and 'bids-nowait', see below. + } + }] + } +}); +``` + + +## Configuration parameters + +{: .table .table-bordered .table-striped } +| Name | Type | Scope | Description | +| :------------ | :------------ | :------------ |:------------ | +| ``cdnUrl`` | ``string`` | Required | CDN URL of the script, which is to be used for protection. | +| ``protectionMode`` | ``'full' \| 'bids' \| 'bids-nowait'`` | Required | Integration mode. Please refer to the "Integration modes" section for details. | + + +## Integration modes + +{: .table .table-bordered .table-striped } +| Integration Mode | Parameter Value | Description | +| :------------ | :------------ | :------------ | +| Full page protection | ``'full'`` | Preferred mode. The module will add the protector agent script directly to the page, and it will protect all placements. This mode will make the most out of various behavioral detection mechanisms, and will also prevent typical malicious behaviors. Please note that in this mode, depending on Prebid library naming, Chrome may mistakenly tag non-ad-related content as ads: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/ad_tagging.md. | +| Bids-only protection | ``'bids'`` | The module will protect specific bid responses, more specifically, the HTML representing ad payload, by wrapping it into the agent script. Please note that in this mode, ads delivered directly, outside of Prebid integration, will not be protected, since the module can only access the ads coming through Prebid. | +| Bids-only protection with no delay on bid rendering | ``'bids-nowait'`` | Same as above, but in this mode, the script will also *not* wrap those bid responses, which arrived prior to successful preloading of agent script. | diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js index 80e4f13e7d4..3c2d3c51bf5 100644 --- a/modules/cleanmedianetBidAdapter.js +++ b/modules/cleanmedianetBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { getDNT, inIframe, isArray, isNumber, logError, deepAccess, logWarn } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; @@ -76,7 +76,7 @@ export const spec = { }, device: { ua: navigator.userAgent, - dnt: utils.getDNT() ? 1 : 0, + dnt: getDNT() ? 1 : 0, h: screen.height, w: screen.width, language: navigator.language @@ -113,7 +113,7 @@ export const spec = { id: transactionId, instl: params.instl === 1 ? 1 : 0, tagid: adUnitCode, - bidfloor: params.bidfloor || 0, + bidfloor: 0, bidfloorcur: 'USD', secure: 1 }; @@ -129,7 +129,7 @@ export const spec = { w: sizes.length ? sizes[0][0] : 300, h: sizes.length ? sizes[0][1] : 250, pos: params.pos || 0, - topframe: utils.inIframe() ? 0 : 1 + topframe: inIframe() ? 0 : 1 } }); rtbBidRequest.imp.push(bannerImp); @@ -147,10 +147,10 @@ export const spec = { }; let playerSize = mediaTypes.video.playerSize || sizes; - if (utils.isArray(playerSize[0])) { + if (isArray(playerSize[0])) { videoImp.video.w = playerSize[0][0]; videoImp.video.h = playerSize[0][1]; - } else if (utils.isNumber(playerSize[0])) { + } else if (isNumber(playerSize[0])) { videoImp.video.w = playerSize[0]; videoImp.video.h = playerSize[1]; } else { @@ -179,7 +179,7 @@ export const spec = { interpretResponse: function (serverResponse, bidRequest) { const response = serverResponse && serverResponse.body; if (!response) { - utils.logError('empty response'); + logError('empty response'); return []; } @@ -203,7 +203,7 @@ export const spec = { }; if ( - utils.deepAccess( + deepAccess( bidRequest.bidRequest, 'mediaTypes.' + outBid.mediaType ) @@ -211,7 +211,7 @@ export const spec = { if (outBid.mediaType === BANNER) { outBids.push(Object.assign({}, outBid, {ad: bid.adm})); } else if (outBid.mediaType === VIDEO) { - const context = utils.deepAccess( + const context = deepAccess( bidRequest.bidRequest, 'mediaTypes.video.context' ); @@ -287,7 +287,7 @@ function newRenderer(bidRequest, bid, rendererOptions = {}) { try { renderer.setRender(renderOutstream); } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); + logWarn('Prebid Error calling setRender on renderer', err); } return renderer; } diff --git a/modules/clickforceBidAdapter.js b/modules/clickforceBidAdapter.js index 20408fe9177..eceb4934b04 100644 --- a/modules/clickforceBidAdapter.js +++ b/modules/clickforceBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { _each } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; const BIDDER_CODE = 'clickforce'; @@ -25,7 +25,7 @@ export const spec = { */ buildRequests: function(validBidRequests) { const bidParams = []; - utils._each(validBidRequests, function(bid) { + _each(validBidRequests, function(bid) { bidParams.push({ z: bid.params.zone, bidId: bid.bidId @@ -51,12 +51,12 @@ export const spec = { const bidRequestList = []; if (typeof bidRequest != 'undefined') { - utils._each(bidRequest.validBidRequests, function(req) { + _each(bidRequest.validBidRequests, function(req) { bidRequestList[req.bidId] = req; }); } - utils._each(serverResponse.body, function(response) { + _each(serverResponse.body, function(response) { if (response.requestId != null) { // native ad size if (response.width == 3) { @@ -88,6 +88,9 @@ export const spec = { impressionTrackers: response.tag.iu, }, mediaType: 'native', + meta: { + advertiserDomains: response.adomain || [] + }, }); } else { // display ad @@ -102,6 +105,9 @@ export const spec = { ttl: response.ttl, ad: response.tag, mediaType: 'banner', + meta: { + advertiserDomains: response.adomain || [] + }, }); } } diff --git a/modules/clicktripzBidAdapter.js b/modules/clicktripzBidAdapter.js deleted file mode 100644 index 2149cbe4527..00000000000 --- a/modules/clicktripzBidAdapter.js +++ /dev/null @@ -1,67 +0,0 @@ -import {logError, _each} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'clicktripz'; -const ENDPOINT_URL = 'https://www.clicktripz.com/x/prebid/v1'; - -export const spec = { - code: BIDDER_CODE, - aliases: ['ctz'], // short code - - isBidRequestValid: function (bid) { - if (bid && bid.params && bid.params.placementId && bid.params.siteId) { - return true; - } - - return false; - }, - - buildRequests: function (validBidRequests) { - let bidRequests = []; - - _each(validBidRequests, function (bid) { - bidRequests.push({ - bidId: bid.bidId, - placementId: bid.params.placementId, - siteId: bid.params.siteId, - sizes: bid.sizes.map(function (size) { - return size.join('x') - }) - }); - }); - return { - method: 'POST', - url: ENDPOINT_URL, - data: bidRequests - }; - }, - - interpretResponse: function (serverResponse) { - let bidResponses = []; - - if (serverResponse && serverResponse.body) { - _each(serverResponse.body, function (bid) { - if (bid.errors) { - logError(bid.errors); - return; - } - - const size = bid.size.split('x'); - bidResponses.push({ - requestId: bid.bidId, - cpm: bid.cpm, - width: size[0], - height: size[1], - creativeId: bid.creativeId, - currency: bid.currency, - netRevenue: bid.netRevenue, - ttl: bid.ttl, - adUrl: bid.adUrl - }); - }); - } - return bidResponses; - } -}; - -registerBidder(spec); diff --git a/modules/codefuelBidAdapter.js b/modules/codefuelBidAdapter.js new file mode 100644 index 00000000000..b9da86ac24e --- /dev/null +++ b/modules/codefuelBidAdapter.js @@ -0,0 +1,181 @@ +import { deepAccess, isArray } from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +const BIDDER_CODE = 'codefuel'; +const CURRENCY = 'USD'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [ BANNER ], + aliases: ['ex'], // short code + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + if (bid.nativeParams) { + return false; + } + return !!(bid.params.placementId || (bid.params.member && bid.params.invCode)); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const page = bidderRequest.refererInfo.referer; + const domain = getDomainFromURL(page) + const ua = navigator.userAgent; + const devicetype = getDeviceType() + const publisher = setOnAny(validBidRequests, 'params.publisher'); + const cur = CURRENCY; + const endpointUrl = 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid' + const timeout = bidderRequest.timeout; + + validBidRequests.forEach(bid => bid.netRevenue = 'net'); + + const imps = validBidRequests.map((bid, idx) => { + const imp = { + id: idx + 1 + '' + } + + if (bid.params.tagid) { + imp.tagid = bid.params.tagid + } + + if (bid.sizes) { + imp.banner = { + format: transformSizes(bid.sizes) + } + } + + return imp; + }); + + const request = { + id: bidderRequest.auctionId, + site: { page, domain, publisher }, + device: { ua, devicetype }, + source: { fd: 1 }, + cur: [cur], + tmax: timeout, + imp: imps, + }; + + return { + method: 'POST', + url: endpointUrl, + data: request, + bids: validBidRequests, + options: { + withCredentials: false + } + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse, { bids }) => { + if (!serverResponse.body) { + return []; + } + const { seatbid, cur } = serverResponse.body; + + const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { + result[bid.impid - 1] = bid; + return result; + }, []); + + return bids.map((bid, id) => { + const bidResponse = bidResponses[id]; + if (bidResponse) { + const bidObject = { + requestId: bid.bidId, + cpm: bidResponse.price, + creativeId: bidResponse.crid, + ttl: 360, + netRevenue: true, + currency: cur, + mediaType: BANNER, + ad: bidResponse.adm, + width: bidResponse.w, + height: bidResponse.h, + meta: { advertiserDomains: bid.adomain ? bid.adomain : [] } + }; + return bidObject; + } + }).filter(Boolean); + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + return []; + } + +} +registerBidder(spec); + +function getDomainFromURL(url) { + let anchor = document.createElement('a'); + anchor.href = url; + return anchor.hostname; +} + +function getDeviceType() { + if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { + return 5; // 'tablet' + } + if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) { + return 4; // 'mobile' + } + return 2; // 'desktop' +} + +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +function flatten(arr) { + return [].concat(...arr); +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + if (!isArray(requestSizes)) { + return []; + } + + if (requestSizes.length === 2 && !isArray(requestSizes[0])) { + return [{ + w: parseInt(requestSizes[0], 10), + h: parseInt(requestSizes[1], 10) + }]; + } else if (isArray(requestSizes[0])) { + return requestSizes.map(item => + ({ + w: parseInt(item[0], 10), + h: parseInt(item[1], 10) + }) + ); + } + + return []; +} diff --git a/modules/zemantaBidAdapter.md b/modules/codefuelBidAdapter.md similarity index 75% rename from modules/zemantaBidAdapter.md rename to modules/codefuelBidAdapter.md index fa933ecd922..4e1a0dd6409 100644 --- a/modules/zemantaBidAdapter.md +++ b/modules/codefuelBidAdapter.md @@ -1,27 +1,27 @@ # Overview ``` -Module Name: Zemanta Adapter +Module Name: Codefuel Adapter Module Type: Bidder Adapter -Maintainer: prog-ops-team@outbrain.com +Maintainer: hayimm@codefuel.com ``` # Description -Module that connects to zemanta bidder to fetch bids. -Both native and display formats are supported but not at the same time. Using OpenRTB standard. +Module that connects to Codefuel bidder to fetch bids. +Display format is supported but not native format. Using OpenRTB standard. # Configuration ## Bidder and usersync URLs -The Zemanta adapter does not work without setting the correct bidder and usersync URLs. +The Codefuel adapter does not work without setting the correct bidder. You will receive the URLs when contacting us. ``` pbjs.setConfig({ - zemanta: { - bidderUrl: 'https://bidder-url.com', + codefuel: { + bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid', usersyncUrl: 'https://usersync-url.com' } }); @@ -58,7 +58,7 @@ pbjs.setConfig({ } }, bids: [{ - bidder: 'zemanta', + bidder: 'codefuel', params: { publisher: { id: '2706', // required @@ -73,8 +73,8 @@ pbjs.setConfig({ ]; pbjs.setConfig({ - zemanta: { - bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + codefuel: { + bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid' } }); ``` @@ -89,7 +89,7 @@ pbjs.setConfig({ } }, bids: [{ - bidder: 'zemanta', + bidder: 'codefuel', params: { publisher: { id: '2706', // required @@ -104,8 +104,8 @@ pbjs.setConfig({ ]; pbjs.setConfig({ - zemanta: { - bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + codefuel: { + bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid' } }); ``` diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js index 43f5cc01ccf..e3d3c65a4f0 100644 --- a/modules/cointrafficBidAdapter.js +++ b/modules/cointrafficBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { parseSizesInput, logError, isEmpty } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import { config } from '../src/config.js' @@ -34,14 +34,14 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { - const sizes = utils.parseSizesInput(bidRequest.params.size || bidRequest.sizes); + const sizes = parseSizesInput(bidRequest.params.size || bidRequest.sizes); const currency = config.getConfig(`currency.bidderCurrencyDefault.${BIDDER_CODE}`) || config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; if (ALLOWED_CURRENCIES.indexOf(currency) === -1) { - utils.logError('Currency is not supported - ' + currency); + logError('Currency is not supported - ' + currency); return; } @@ -72,7 +72,7 @@ export const spec = { const bidResponses = []; const response = serverResponse.body; - if (utils.isEmpty(response)) { + if (isEmpty(response)) { return bidResponses; } @@ -85,7 +85,11 @@ export const spec = { height: response.height, creativeId: response.creativeId, ttl: response.ttl, - ad: response.ad + ad: response.ad, + meta: { + advertiserDomains: response.adomain && response.adomain.length ? response.adomain : [], + mediaType: response.mediaType + } }; bidResponses.push(bidResponse); diff --git a/modules/coinzillaBidAdapter.js b/modules/coinzillaBidAdapter.js index 240a3f1fcde..cd087daa8cb 100644 --- a/modules/coinzillaBidAdapter.js +++ b/modules/coinzillaBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { parseSizesInput } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -31,7 +31,7 @@ export const spec = { return []; } return validBidRequests.map(bidRequest => { - const sizes = utils.parseSizesInput(bidRequest.params.size || bidRequest.sizes)[0]; + const sizes = parseSizesInput(bidRequest.params.size || bidRequest.sizes)[0]; const width = sizes.split('x')[0]; const height = sizes.split('x')[1]; const payload = { @@ -79,7 +79,11 @@ export const spec = { netRevenue: netRevenue, ttl: config.getConfig('_bidderTimeout'), referrer: referrer, - ad: response.ad + ad: response.ad, + mediaType: response.mediaType, + meta: { + advertiserDomains: response.advertiserDomain || [] + } }; bidResponses.push(bidResponse); } diff --git a/modules/collectcentBidAdapter.js b/modules/collectcentBidAdapter.js deleted file mode 100644 index add3e06430d..00000000000 --- a/modules/collectcentBidAdapter.js +++ /dev/null @@ -1,93 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; - -const BIDDER_CODE = 'collectcent'; -const URL_MULTI = 'https://publishers.motionspots.com/?c=o&m=multi'; -const URL_SYNC = 'https://publishers.motionspots.com/?c=o&m=cookie'; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], - - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && - bid.params && - !isNaN(bid.params.placementId) && - spec.supportedMediaTypes.indexOf(bid.params.traffic) !== -1 - ); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: (validBidRequests, bidderRequest) => { - let winTop; - try { - winTop = window.top; - } catch (e) { - utils.logMessage(e); - winTop = window; - }; - - const placements = []; - const location = bidderRequest ? new URL(bidderRequest.refererInfo.referer) : winTop.location; - const request = { - 'secure': (location.protocol === 'https:') ? 1 : 0, - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - - for (let i = 0; i < validBidRequests.length; i++) { - const bid = validBidRequests[i]; - const params = bid.params; - placements.push({ - placementId: params.placementId, - bidId: bid.bidId, - sizes: bid.sizes, - traffic: params.traffic - }); - } - return { - method: 'POST', - url: URL_MULTI, - data: request - }; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: (serverResponse) => { - try { - serverResponse = serverResponse.body; - } catch (e) { - utils.logMessage(e); - }; - return serverResponse; - }, - - getUserSyncs: () => { - return [{ - type: 'image', - url: URL_SYNC - }]; - } -}; - -registerBidder(spec); diff --git a/modules/colombiaBidAdapter.js b/modules/colombiaBidAdapter.js deleted file mode 100644 index 55109dbaab2..00000000000 --- a/modules/colombiaBidAdapter.js +++ /dev/null @@ -1,82 +0,0 @@ -import * as utils from '../src/utils.js'; -import {config} from '../src/config.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -const BIDDER_CODE = 'colombia'; -const ENDPOINT_URL = 'https://ade.clmbtech.com/cde/prebid.htm'; -const HOST_NAME = document.location.protocol + '//' + window.location.host; - -export const spec = { - code: BIDDER_CODE, - aliases: ['clmb'], - supportedMediaTypes: [BANNER], - isBidRequestValid: function(bid) { - return !!(bid.params.placementId); - }, - buildRequests: function(validBidRequests, bidderRequest) { - return validBidRequests.map(bidRequest => { - const params = bidRequest.params; - const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; - const width = sizes.split('x')[0]; - const height = sizes.split('x')[1]; - const placementId = params.placementId; - const cb = Math.floor(Math.random() * 99999999999); - const bidId = bidRequest.bidId; - const referrer = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.referer : ''; - const payload = { - v: 'hb1', - p: placementId, - w: width, - h: height, - cb: cb, - r: referrer, - uid: bidId, - t: 'i', - d: HOST_NAME, - }; - return { - method: 'POST', - url: ENDPOINT_URL, - data: payload, - } - }); - }, - interpretResponse: function(serverResponse, bidRequest) { - const bidResponses = []; - const response = serverResponse.body; - const crid = response.creativeId || 0; - const width = response.width || 0; - const height = response.height || 0; - let cpm = response.cpm || 0; - if (width == 300 && height == 250) { - cpm = cpm * 0.2; - } - if (width == 320 && height == 50) { - cpm = cpm * 0.55; - } - if (cpm <= 0) { - return bidResponses; - } - if (width !== 0 && height !== 0 && cpm !== 0 && crid !== 0) { - const dealId = response.dealid || ''; - const currency = response.currency || 'USD'; - const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; - const bidResponse = { - requestId: bidRequest.data.uid, - cpm: cpm, - width: response.width, - height: response.height, - creativeId: crid, - dealId: dealId, - currency: currency, - netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), - referrer: bidRequest.data.r, - ad: response.ad - }; - bidResponses.push(bidResponse); - } - return bidResponses; - } -} -registerBidder(spec); diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 1afb343566d..72df7c7b465 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -1,6 +1,7 @@ +import { getWindowTop, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; const BIDDER_CODE = 'colossusssp'; const G_URL = 'https://colossusssp.com/?c=o&m=multi'; @@ -46,7 +47,10 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placement_id)); + const validPlacamentId = bid.params && !isNaN(bid.params.placement_id); + const validGroupId = bid.params && !isNaN(bid.params.group_id); + + return Boolean(bid.bidId && (validPlacamentId || validGroupId)); }, /** @@ -56,17 +60,17 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { - const winTop = utils.getWindowTop(); + const winTop = getWindowTop(); const location = winTop.location; let placements = []; let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language : '', - 'secure': location.protocol === 'https:' ? 1 : 0, - 'host': location.host, - 'page': location.pathname, - 'placements': placements, + deviceWidth: winTop.screen.width, + deviceHeight: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language : '', + secure: location.protocol === 'https:' ? 1 : 0, + host: location.host, + page: location.pathname, + placements: placements, }; if (bidderRequest) { @@ -84,6 +88,7 @@ export const spec = { let traff = bid.params.traffic || BANNER let placement = { placementId: bid.params.placement_id, + groupId: bid.params.group_id, bidId: bid.bidId, sizes: bid.mediaTypes[traff].sizes, traffic: traff, @@ -106,14 +111,36 @@ export const spec = { if (bid.schain) { placement.schain = bid.schain; } + let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + placement.gpid = gpid; + } if (bid.userId) { getUserId(placement.eids, bid.userId.britepoolid, 'britepool.com'); getUserId(placement.eids, bid.userId.idl_env, 'identityLink'); - getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com') + getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com'); + getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); getUserId(placement.eids, bid.userId.tdid, 'adserver.org', { rtiPartner: 'TDID' }); } + if (traff === VIDEO) { + placement.playerSize = bid.mediaTypes[VIDEO].playerSize; + placement.minduration = bid.mediaTypes[VIDEO].minduration; + placement.maxduration = bid.mediaTypes[VIDEO].maxduration; + placement.mimes = bid.mediaTypes[VIDEO].mimes; + placement.protocols = bid.mediaTypes[VIDEO].protocols; + placement.startdelay = bid.mediaTypes[VIDEO].startdelay; + placement.placement = bid.mediaTypes[VIDEO].placement; + placement.skip = bid.mediaTypes[VIDEO].skip; + placement.skipafter = bid.mediaTypes[VIDEO].skipafter; + placement.minbitrate = bid.mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = bid.mediaTypes[VIDEO].maxbitrate; + placement.delivery = bid.mediaTypes[VIDEO].delivery; + placement.playbackmethod = bid.mediaTypes[VIDEO].playbackmethod; + placement.api = bid.mediaTypes[VIDEO].api; + placement.linearity = bid.mediaTypes[VIDEO].linearity; + } placements.push(placement); } return { @@ -136,11 +163,14 @@ export const spec = { for (let i = 0; i < serverResponse.length; i++) { let resItem = serverResponse[i]; if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + response.push(resItem); } } } catch (e) { - utils.logMessage(e); + logMessage(e); }; return response; }, @@ -150,6 +180,12 @@ export const spec = { type: 'image', url: G_URL_SYNC }]; + }, + + onBidWon: (bid) => { + if (bid.nurl) { + ajax(bid.nurl, null); + } } }; diff --git a/modules/colossussspBidAdapter.md b/modules/colossussspBidAdapter.md index d95080546c2..4187dfbf36e 100644 --- a/modules/colossussspBidAdapter.md +++ b/modules/colossussspBidAdapter.md @@ -13,19 +13,32 @@ Module that connects to Colossus SSP demand sources # Test Parameters ``` var adUnits = [{ - code: 'placementid_0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [{ - bidder: 'colossusssp', - params: { - placement_id: 0, - traffic: 'banner' - } - }] - } - ]; + code: 'placementid_0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'colossusssp', + params: { + placement_id: 0, + traffic: 'banner' + } + }] + }, { + code: 'placementid_1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'colossusssp', + params: { + group_id: 0, + traffic: 'banner' + } + }] + }]; ``` diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js new file mode 100644 index 00000000000..77f918276bc --- /dev/null +++ b/modules/compassBidAdapter.js @@ -0,0 +1,208 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'compass'; +const AD_URL = 'https://sa-lb.deliverimp.com/pbjs'; +const SYNC_URL = 'https://sa-cs.deliverimp.com'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/compassBidAdapter.md b/modules/compassBidAdapter.md new file mode 100644 index 00000000000..18d52c12384 --- /dev/null +++ b/modules/compassBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Compass Bidder Adapter +Module Type: Compass Bidder Adapter +Maintainer: sa-support@brightcom.com +``` + +# Description + +Connects to Compass exchange for bids. +Compass bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'compass', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'compass', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'compass', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/concertAnalyticsAdapter.js b/modules/concertAnalyticsAdapter.js index a81d07e63b5..cd52a2ffabf 100644 --- a/modules/concertAnalyticsAdapter.js +++ b/modules/concertAnalyticsAdapter.js @@ -1,8 +1,8 @@ +import { logMessage } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -import * as utils from '../src/utils.js'; const analyticsType = 'endpoint'; @@ -90,7 +90,7 @@ function sendEvents() { if (!queue.length) return; if (!pageIncludedInSample) { - utils.logMessage('Page not included in sample for Concert Analytics'); + logMessage('Page not included in sample for Concert Analytics'); return; } @@ -100,7 +100,7 @@ function sendEvents() { contentType: 'application/json', method: 'POST' }); - } catch (err) { utils.logMessage('Concert Analytics error') } + } catch (err) { logMessage('Concert Analytics error') } } // save the base class function diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index 60634151aba..9a55e9cef1d 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -1,5 +1,4 @@ - -import * as utils from '../src/utils.js'; +import { logWarn, logMessage, debugTurnedOn, generateUUID } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js' @@ -17,7 +16,7 @@ export const spec = { */ isBidRequestValid: function(bid) { if (!bid.params.partnerId) { - utils.logWarn('Missing partnerId bid parameter'); + logWarn('Missing partnerId bid parameter'); return false; } @@ -32,14 +31,14 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - utils.logMessage(validBidRequests); - utils.logMessage(bidderRequest); + logMessage(validBidRequests); + logMessage(bidderRequest); let payload = { meta: { prebidVersion: '$prebid.version$', pageUrl: bidderRequest.refererInfo.referer, screen: [window.screen.width, window.screen.height].join('x'), - debug: utils.debugTurnedOn(), + debug: debugTurnedOn(), uid: getUid(bidderRequest), optedOut: hasOptedOutOfPersonalization(), adapterVersion: '1.1.1', @@ -64,7 +63,7 @@ export const spec = { return slot; }); - utils.logMessage(payload); + logMessage(payload); return { method: 'POST', @@ -79,8 +78,8 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest) { - utils.logMessage(serverResponse); - utils.logMessage(bidRequest); + logMessage(serverResponse); + logMessage(bidRequest); const serverBody = serverResponse.body; @@ -105,11 +104,11 @@ export const spec = { } }); - if (utils.debugTurnedOn() && serverBody.debug) { - utils.logMessage(`CONCERT`, serverBody.debug); + if (debugTurnedOn() && serverBody.debug) { + logMessage(`CONCERT`, serverBody.debug); } - utils.logMessage(bidResponses); + logMessage(bidResponses); return bidResponses; }, @@ -150,8 +149,8 @@ export const spec = { * @param {data} Containing timeout specific data */ onTimeout: function(data) { - utils.logMessage('concert bidder timed out'); - utils.logMessage(data); + logMessage('concert bidder timed out'); + logMessage(data); }, /** @@ -159,8 +158,8 @@ export const spec = { * @param {Bid} The bid that won the auction */ onBidWon: function(bid) { - utils.logMessage('concert bidder won bid'); - utils.logMessage(bid); + logMessage('concert bidder won bid'); + logMessage(bid); } } @@ -182,7 +181,7 @@ function getUid(bidderRequest) { let uid = storage.getDataFromLocalStorage(CONCERT_UID_KEY); if (!uid) { - uid = utils.generateUUID(); + uid = generateUUID(); storage.setDataInLocalStorage(CONCERT_UID_KEY, uid); } diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js new file mode 100644 index 00000000000..ac44a8b5a2d --- /dev/null +++ b/modules/connectIdSystem.js @@ -0,0 +1,104 @@ +/** + * This module adds support for Yahoo ConnectID to the user ID module system. + * The {@link module:modules/userId} module is required + * @module modules/connectIdSystem + * @requires module:modules/userId + */ + +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; +import {logError, formatQS} from '../src/utils.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +const MODULE_NAME = 'connectId'; +const VENDOR_ID = 25; +const PLACEHOLDER = '__PIXEL_ID__'; +const UPS_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; + +function isEUConsentRequired(consentData) { + return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); +} + +/** @type {Submodule} */ +export const connectIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * @type {Number} + */ + gvlid: VENDOR_ID, + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{connectId: string} | undefined} + */ + decode(value) { + return (typeof value === 'object' && value.connectid) + ? {connectId: value.connectid} : undefined; + }, + /** + * Gets the Yahoo ConnectID + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData} [consentData] + * @returns {IdResponse|undefined} + */ + getId(config, consentData) { + const params = config.params || {}; + if (!params || typeof params.he !== 'string' || + (typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) { + logError('The connectId submodule requires the \'he\' and \'pixelId\' parameters to be defined.'); + return; + } + + const data = { + '1p': includes([1, '1', true], params['1p']) ? '1' : '0', + he: params.he, + gdpr: isEUConsentRequired(consentData) ? '1' : '0', + gdpr_consent: isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', + us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : '' + }; + + if (params.pixelId) { + data.pixelId = params.pixelId + } + + const resp = function (callback) { + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + logError(error); + } + } + callback(responseObj); + }, + error: error => { + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + const endpoint = UPS_ENDPOINT.replace(PLACEHOLDER, params.pixelId); + let url = `${params.endpoint || endpoint}?${formatQS(data)}`; + connectIdSubmodule.getAjaxFn()(url, callbacks, null, {method: 'GET', withCredentials: true}); + }; + return {callback: resp}; + }, + + /** + * Return the function used to perform XHR calls. + * Utilised for each of testing. + * @returns {Function} + */ + getAjaxFn() { + return ajax; + } +}; + +submodule('userId', connectIdSubmodule); diff --git a/modules/connectIdSystem.md b/modules/connectIdSystem.md new file mode 100644 index 00000000000..f2153e1b6cb --- /dev/null +++ b/modules/connectIdSystem.md @@ -0,0 +1,33 @@ +## Yahoo ConnectID User ID Submodule + +Yahoo ConnectID user ID Module. + +### Prebid Params + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'connectId', + storage: { + name: 'connectId', + type: 'html5', + expires: 15 + }, + params: { + pixelId: 58776, + he: '0bef996248d63cea1529cb86de31e9547a712d9f380146e98bbd39beec70355a' + } + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the Yahoo ConnectID user ID Module. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the Yahoo ConnectID module - `"connectId"` | `"connectId"` | +| params | Required | Object | Data for Yahoo ConnectID initialization. | | +| params.pixelId | Required | Number | The Yahoo supplied publisher specific pixel Id | `8976` | +| params.he | Required | String | The SHA-256 hashed user email address | `"529cb86de31e9547a712d9f380146e98bbd39beec"` | diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index 4fa2a56a004..711afd98d0f 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { deepSetValue, convertTypes, tryAppendQueryString, logWarn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import {config} from '../src/config.js'; @@ -47,12 +47,12 @@ export const spec = { // coppa compliance if (config.getConfig('coppa') === true) { - utils.deepSetValue(data, 'user.coppa', 1); + deepSetValue(data, 'user.coppa', 1); } // adding schain object if (validBidRequests[0].schain) { - utils.deepSetValue(data, 'source.ext.schain', validBidRequests[0].schain); + deepSetValue(data, 'source.ext.schain', validBidRequests[0].schain); } // Attaching GDPR Consent Params @@ -61,18 +61,18 @@ export const spec = { if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; } - utils.deepSetValue(data, 'user.ext.gdpr', gdprApplies); - utils.deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(data, 'user.ext.gdpr', gdprApplies); + deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } // CCPA if (bidderRequest.uspConsent) { - utils.deepSetValue(data, 'user.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(data, 'user.ext.us_privacy', bidderRequest.uspConsent); } // EIDS Support if (validBidRequests[0].userId) { - utils.deepSetValue(data, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); + deepSetValue(data, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); } validBidRequests.map(bid => { @@ -124,6 +124,7 @@ export const spec = { bid.width = decision.width; bid.height = decision.height; bid.dealid = decision.dealid || null; + bid.meta = { advertiserDomains: decision && decision.adomain ? decision.adomain : [] }; bid.ad = retrieveAd(decision); bid.currency = 'USD'; bid.creativeId = decision.adId; @@ -138,7 +139,7 @@ export const spec = { }, transformBidParams: function (params, isOpenRtb) { - return utils.convertTypes({ + return convertTypes({ 'siteId': 'number', 'networkId': 'number' }, params); @@ -148,19 +149,19 @@ export const spec = { let syncEndpoint = 'https://cdn.connectad.io/connectmyusers.php?'; if (gdprConsent) { - syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); + syncEndpoint = tryAppendQueryString(syncEndpoint, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); } if (gdprConsent && typeof gdprConsent.consentString === 'string') { - syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'gdpr_consent', gdprConsent.consentString); + syncEndpoint = tryAppendQueryString(syncEndpoint, 'gdpr_consent', gdprConsent.consentString); } if (uspConsent) { - syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'us_privacy', uspConsent); + syncEndpoint = tryAppendQueryString(syncEndpoint, 'us_privacy', uspConsent); } if (config.getConfig('coppa') === true) { - syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'coppa', 1); + syncEndpoint = tryAppendQueryString(syncEndpoint, 'coppa', 1); } if (syncOptions.iframeEnabled) { @@ -169,7 +170,7 @@ export const spec = { url: syncEndpoint }]; } else { - utils.logWarn('Bidder ConnectAd: Please activate iFrame Sync'); + logWarn('Bidder ConnectAd: Please activate iFrame Sync'); } } }; diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 6a13e73b8a2..ecd0c0eec4b 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -1,10 +1,11 @@ + /** * 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 * as utils from '../src/utils.js'; +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'; @@ -98,7 +99,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { } function v2CmpResponseCallback(tcfData, success) { - utils.logInfo('Received a response from CMP', tcfData); + logInfo('Received a response from CMP', tcfData); if (success) { if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { cmpSuccess(tcfData, hookConfig); @@ -113,7 +114,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { function afterEach() { if (cmpResponse.getConsentData && cmpResponse.getVendorConsents) { - utils.logInfo('Received all requested responses from CMP', cmpResponse); + logInfo('Received all requested responses from CMP', cmpResponse); cmpSuccess(cmpResponse, hookConfig); } } @@ -147,8 +148,8 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { // else assume prebid may be inside an iframe and use the IAB CMP locator code to see if CMP's located in a higher parent window. this works in cross domain iframes // if the CMP is not found, the iframe function will call the cmpError exit callback to abort the rest of the CMP workflow - if (utils.isFn(cmpFunction)) { - utils.logInfo('Detected CMP API is directly accessible, calling it now...'); + if (isFn(cmpFunction)) { + logInfo('Detected CMP API is directly accessible, calling it now...'); if (cmpVersion === 1) { cmpFunction('getConsentData', null, v1CallbackHandler.consentDataCallback); cmpFunction('getVendorConsents', null, v1CallbackHandler.vendorConsentsCallback); @@ -157,11 +158,11 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { } } else if (cmpVersion === 1 && inASafeFrame() && typeof window.$sf.ext.cmp === 'function') { // this safeframe workflow is only supported with TCF v1 spec; the v2 recommends to use the iframe postMessage route instead (even if you are in a safeframe). - utils.logInfo('Detected Prebid.js is encased in a SafeFrame and CMP is registered, calling it now...'); + logInfo('Detected Prebid.js is encased in a SafeFrame and CMP is registered, calling it now...'); callCmpWhileInSafeFrame('getConsentData', v1CallbackHandler.consentDataCallback); callCmpWhileInSafeFrame('getVendorConsents', v1CallbackHandler.vendorConsentsCallback); } else { - utils.logInfo('Detected CMP is outside the current iframe where Prebid.js is located, calling it now...'); + logInfo('Detected CMP is outside the current iframe where Prebid.js is located, calling it now...'); if (cmpVersion === 1) { callCmpWhileInIframe('getConsentData', cmpFrame, v1CallbackHandler.consentDataCallback); callCmpWhileInIframe('getVendorConsents', cmpFrame, v1CallbackHandler.vendorConsentsCallback); @@ -187,7 +188,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { let width = 1; let height = 1; if (Array.isArray(adUnits) && adUnits.length > 0) { - let sizes = utils.getAdUnitSizes(adUnits[0]); + let sizes = getAdUnitSizes(adUnits[0]); width = sizes[0][0]; height = sizes[0][1]; } @@ -282,12 +283,12 @@ export function requestBidsHook(fn, reqBidsConfigObj) { // in case we already have consent (eg during bid refresh) if (consentData) { - utils.logInfo('User consent information already known. Pulling internally stored information...'); + logInfo('User consent information already known. Pulling internally stored information...'); return exitModule(null, hookConfig); } if (!includes(Object.keys(cmpCallMap), userCMP)) { - utils.logWarn(`CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); + logWarn(`CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args); } @@ -316,8 +317,8 @@ function processCmpData(consentObject, hookConfig) { return !!( (typeof gdprApplies !== 'boolean') || (gdprApplies === true && - !(utils.isStr(consentObject.getConsentData.consentData) && - utils.isPlainObject(consentObject.getVendorConsents) && + !(isStr(consentObject.getConsentData.consentData) && + isPlainObject(consentObject.getVendorConsents) && Object.keys(consentObject.getVendorConsents).length > 1 ) ) @@ -330,7 +331,7 @@ function processCmpData(consentObject, hookConfig) { let tcString = consentObject && consentObject.tcString; return !!( (typeof gdprApplies !== 'boolean') || - (gdprApplies === true && !utils.isStr(tcString)) + (gdprApplies === true && !isStr(tcString)) ); } @@ -348,12 +349,12 @@ function processCmpData(consentObject, hookConfig) { // Raise deprecation warning if 'allowAuctionWithoutConsent' is used with TCF 2. if (allowAuction.definedInConfig && cmpVersion === 2) { - utils.logWarn(`'allowAuctionWithoutConsent' ignored for TCF 2`); + logWarn(`'allowAuctionWithoutConsent' ignored for TCF 2`); } else if (!allowAuction.definedInConfig && cmpVersion === 1) { - utils.logInfo(`'allowAuctionWithoutConsent' using system default: (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); + logInfo(`'allowAuctionWithoutConsent' using system default: (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); } - if (utils.isFn(checkFn)) { + if (isFn(checkFn)) { if (checkFn(consentObject)) { cmpFailed(`CMP returned unexpected value during lookup process.`, hookConfig, consentObject); } else { @@ -370,7 +371,13 @@ function processCmpData(consentObject, hookConfig) { * General timeout callback when interacting with CMP takes too long. */ function cmpTimedOut(hookConfig) { - cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig); + if (cmpVersion === 2) { + logWarn(`No response from CMP, continuing auction...`) + storeConsentData(undefined); + exitModule(null, hookConfig) + } else { + cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig); + } } /** @@ -406,7 +413,7 @@ function storeConsentData(cmpConsentObject) { vendorData: (cmpConsentObject) || undefined, gdprApplies: cmpConsentObject && typeof cmpConsentObject.gdprApplies === 'boolean' ? cmpConsentObject.gdprApplies : gdprScope }; - if (cmpConsentObject && cmpConsentObject.addtlConsent && utils.isStr(cmpConsentObject.addtlConsent)) { + if (cmpConsentObject && cmpConsentObject.addtlConsent && isStr(cmpConsentObject.addtlConsent)) { consentData.addtlConsent = cmpConsentObject.addtlConsent; }; } @@ -441,14 +448,14 @@ function exitModule(errMsg, hookConfig, extraArgs) { if (errMsg) { if (allowAuction.value && cmpVersion === 1) { - utils.logWarn(errMsg + ` 'allowAuctionWithoutConsent' activated.`, extraArgs); + logWarn(errMsg + ` 'allowAuctionWithoutConsent' activated.`, extraArgs); nextFn.apply(context, args); } else { - utils.logError(errMsg + ' Canceling auction as per consentManagement config.', extraArgs); + logError(errMsg + ' Canceling auction as per consentManagement config.', extraArgs); if (typeof hookConfig.bidsBackHandler === 'function') { hookConfig.bidsBackHandler(); } else { - utils.logError('Error executing bidsBackHandler'); + logError('Error executing bidsBackHandler'); } } } else { @@ -476,21 +483,21 @@ export function setConsentConfig(config) { // else for backward compatability, just use `config` config = config && (config.gdpr || config.usp ? config.gdpr : config); if (!config || typeof config !== 'object') { - utils.logWarn('consentManagement config not defined, exiting consent manager'); + logWarn('consentManagement config not defined, exiting consent manager'); return; } - if (utils.isStr(config.cmpApi)) { + if (isStr(config.cmpApi)) { userCMP = config.cmpApi; } else { userCMP = DEFAULT_CMP; - utils.logInfo(`consentManagement config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); + logInfo(`consentManagement config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); } - if (utils.isNumber(config.timeout)) { + if (isNumber(config.timeout)) { consentTimeout = config.timeout; } else { consentTimeout = DEFAULT_CONSENT_TIMEOUT; - utils.logInfo(`consentManagement config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); + logInfo(`consentManagement config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); } if (typeof config.allowAuctionWithoutConsent === 'boolean') { @@ -501,14 +508,14 @@ export function setConsentConfig(config) { // if true, then gdprApplies should be set to true gdprScope = config.defaultGdprScope === true; - utils.logInfo('consentManagement module has been activated...'); + logInfo('consentManagement module has been activated...'); if (userCMP === 'static') { - if (utils.isPlainObject(config.consentData)) { + if (isPlainObject(config.consentData)) { staticConsentData = config.consentData; consentTimeout = 0; } else { - utils.logError(`consentManagement config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); + logError(`consentManagement config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); } } if (!addedConsentHook) { diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index cba9c2758d0..4a4c4ae0a55 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -4,7 +4,7 @@ * information and make it available for any USP (CCPA) supported adapters to * read/pass this information to their system. */ -import * as utils from '../src/utils.js'; +import { isFn, logInfo, logWarn, isStr, isNumber, isPlainObject, logError } from '../src/utils.js'; import { config } from '../src/config.js'; import { uspDataHandler } from '../src/adapterManager.js'; @@ -111,15 +111,15 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { // - else assume prebid is in an iframe, and use the locator to see if the CMP is located in a higher parent window. This works in cross domain iframes. // - if USPAPI is not found, the iframe function will call the uspError exit callback to abort the rest of the USPAPI workflow - if (utils.isFn(uspapiFunction)) { - utils.logInfo('Detected USP CMP is directly accessible, calling it now...'); + if (isFn(uspapiFunction)) { + logInfo('Detected USP CMP is directly accessible, calling it now...'); uspapiFunction( 'getUSPData', USPAPI_VERSION, callbackHandler.consentDataCallback ); } else { - utils.logInfo( + logInfo( 'Detected USP CMP is outside the current iframe where Prebid.js is located, calling it now...' ); callUspApiWhileInIframe( @@ -185,7 +185,7 @@ export function requestBidsHook(fn, reqBidsConfigObj) { }; if (!uspCallMap[consentAPI]) { - utils.logWarn(`USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`); + logWarn(`USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`); return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args); } @@ -275,7 +275,7 @@ function exitModule(errMsg, hookConfig, extraArgs) { let nextFn = hookConfig.nextFn; if (errMsg) { - utils.logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.', extraArgs); + logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.', extraArgs); } nextFn.apply(context, args); } @@ -297,31 +297,31 @@ export function resetConsentData() { export function setConsentConfig(config) { config = config && config.usp; if (!config || typeof config !== 'object') { - utils.logWarn('consentManagement.usp config not defined, exiting usp consent manager'); + logWarn('consentManagement.usp config not defined, exiting usp consent manager'); return; } - if (utils.isStr(config.cmpApi)) { + if (isStr(config.cmpApi)) { consentAPI = config.cmpApi; } else { consentAPI = DEFAULT_CONSENT_API; - utils.logInfo(`consentManagement.usp config did not specify cmpApi. Using system default setting (${DEFAULT_CONSENT_API}).`); + logInfo(`consentManagement.usp config did not specify cmpApi. Using system default setting (${DEFAULT_CONSENT_API}).`); } - if (utils.isNumber(config.timeout)) { + if (isNumber(config.timeout)) { consentTimeout = config.timeout; } else { consentTimeout = DEFAULT_CONSENT_TIMEOUT; - utils.logInfo(`consentManagement.usp config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); + logInfo(`consentManagement.usp config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); } - utils.logInfo('USPAPI consentManagement module has been activated...'); + logInfo('USPAPI consentManagement module has been activated...'); if (consentAPI === 'static') { - if (utils.isPlainObject(config.consentData) && utils.isPlainObject(config.consentData.getUSPData)) { + if (isPlainObject(config.consentData) && isPlainObject(config.consentData.getUSPData)) { if (config.consentData.getUSPData.uspString) staticConsentData = { usPrivacy: config.consentData.getUSPData.uspString }; consentTimeout = 0; } else { - utils.logError(`consentManagement config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); + logError(`consentManagement config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); } } if (!addedConsentHook) { diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index 8eb56f7d0c2..1a2845ba85b 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { logWarn, createTrackPixelHtml } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'consumable'; @@ -122,6 +122,7 @@ 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; @@ -144,7 +145,7 @@ export const spec = { if (syncOptions.pixelEnabled && serverResponses.length > 0) { return serverResponses[0].body.pixels; } else { - utils.logWarn(bidder + ': Please enable iframe based user syncing.'); + logWarn(bidder + ': Please enable iframe based user syncing.'); } } }; @@ -206,7 +207,7 @@ function getSize(sizes) { } function retrieveAd(decision, unitId, unitName) { - let ad = decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); + let ad = decision.contents && decision.contents[0] && decision.contents[0].body + createTrackPixelHtml(decision.impressionUrl); return ad; } diff --git a/modules/consumableBidAdapter.md b/modules/consumableBidAdapter.md index 2189494ebd4..ba472899c49 100644 --- a/modules/consumableBidAdapter.md +++ b/modules/consumableBidAdapter.md @@ -4,7 +4,7 @@ Module Name: Consumable Bid Adapter Module Type: Consumable Adapter -Maintainer: naffis@consumable.com +Maintainer: prebid@consumable.com # Description diff --git a/modules/contentexchangeBidAdapter.js b/modules/contentexchangeBidAdapter.js new file mode 100644 index 00000000000..b3a5056f816 --- /dev/null +++ b/modules/contentexchangeBidAdapter.js @@ -0,0 +1,209 @@ +import { isFn, deepAccess, logMessage } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; + +const BIDDER_CODE = 'contentexchange'; +const AD_URL = 'https://eu2.adnetwork.agency/pbjs'; +const SYNC_URL = 'https://sync2.adnetwork.agency'; + +function isBidResponseValid (bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency || !bid.meta) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData (bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, adFormat } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + placementId, + bidId, + adFormat, + schain, + bidfloor + }; + + switch (adFormat) { + case BANNER: + placement.sizes = mediaTypes[BANNER].sizes; + break; + case VIDEO: + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + break; + case NATIVE: + placement.native = mediaTypes[NATIVE]; + break; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && + params && + params.placementId && + params.adFormat + ); + switch (params.adFormat) { + case BANNER: + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + break; + case VIDEO: + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + break; + case NATIVE: + valid = valid && Boolean(mediaTypes[NATIVE]); + break; + default: + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/contentexchangeBidAdapter.md b/modules/contentexchangeBidAdapter.md new file mode 100644 index 00000000000..445d9c928bf --- /dev/null +++ b/modules/contentexchangeBidAdapter.md @@ -0,0 +1,83 @@ +# Overview + +``` +Module Name: Contentexchange Bidder Adapter +Module Type: Contentexchange Bidder Adapter +Maintainer: no-reply@vsn.si +``` + +# Description + +Connects to Contentexchange exchange for bids. + +Contentexchange bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'contentexchange', + params: { + placementId: '0', + adFormat: 'banner' + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'contentexchange', + params: { + placementId: '0', + adFormat: 'video' + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'contentexchange', + params: { + placementId: '0', + adFormat: 'native' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/convergeBidAdapter.js b/modules/convergeBidAdapter.js deleted file mode 100644 index bea3b6cb1ab..00000000000 --- a/modules/convergeBidAdapter.js +++ /dev/null @@ -1,313 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'converge'; -const ENDPOINT_URL = 'https://tech.convergd.com/hb'; -const TIME_TO_LIVE = 360; -const SYNC_URL = 'https://tech.convergd.com/push_sync'; -const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; - -let hasSynced = false; - -const LOG_ERROR_MESS = { - noAuid: 'Bid from response has no auid parameter - ', - noAdm: 'Bid from response has no adm parameter - ', - noBid: 'Array of bid objects is empty', - noPlacementCode: "Can't find in requested bids the bid with auid - ", - emptyUids: 'Uids should be not empty', - emptySeatbid: 'Seatbid array from response has empty item', - emptyResponse: 'Response is empty', - 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 ], - /** - * 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) { - return !!bid.params.uid; - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests - an array of bids - * @param {bidderRequest} bidderRequest - bidder request object - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests, bidderRequest) { - const auids = []; - const bidsMap = {}; - const slotsMapByUid = {}; - const sizeMap = {}; - const bids = validBidRequests || []; - let priceType = 'net'; - let pageKeywords; - let reqId; - - bids.forEach(bid => { - if (bid.params.priceType === 'gross') { - priceType = 'gross'; - } - reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode} = bid; - auids.push(uid); - const sizesId = utils.parseSizesInput(bid.sizes); - - if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { - const keywords = utils.transformBidderParamKeywords(bid.params.keywords); - - if (keywords.length > 0) { - keywords.forEach(deleteValues); - } - pageKeywords = keywords; - } - - if (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; - } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; - } else { - slotsMap[adUnitCode].bids.push(bid); - } - const slot = slotsMap[adUnitCode]; - - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; - } - - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); - } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); - }); - - const payload = { - pt: priceType, - auids: auids.join(','), - sizes: utils.getKeys(sizeMap).join(','), - r: reqId, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' - }; - - if (pageKeywords) { - payload.keywords = JSON.stringify(pageKeywords); - } - - if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; - } - if (bidderRequest.timeout) { - payload.wtimeout = bidderRequest.timeout; - } - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - payload.gdpr_applies = - (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') - ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; - } - if (bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; - } - } - - return { - method: 'GET', - url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), - bidsMap: bidsMap, - }; - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @param {*} bidRequest - * @param {Renderer} RendererConst - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { - serverResponse = serverResponse && serverResponse.body; - const bidResponses = []; - const bidsMap = bidRequest.bidsMap; - const priceType = bidRequest.data.pt; - - let errorMessage; - - if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; - else if (serverResponse.seatbid && !serverResponse.seatbid.length) { - errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; - } - - if (!errorMessage && serverResponse.seatbid) { - serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses, RendererConst); - }); - } - if (errorMessage) utils.logError(errorMessage); - return bidResponses; - }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - if (!hasSynced && syncOptions.pixelEnabled) { - let params = ''; - - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - params += `&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent) { - params += `&us_privacy=${uspConsent}`; - } - - hasSynced = true; - return { - type: 'image', - url: SYNC_URL + params - }; - } - } -}; - -function isPopulatedArray(arr) { - return !!(utils.isArray(arr) && arr.length > 0); -} - -function deleteValues(keyPairObj) { - if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { - delete keyPairObj.value; - } -} - -function _getBidFromResponse(respItem) { - if (!respItem) { - utils.logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!respItem.bid) { - utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); - } else if (!respItem.bid[0]) { - utils.logError(LOG_ERROR_MESS.noBid); - } - return respItem && respItem.bid && respItem.bid[0]; -} - -function _addBidResponse(serverBid, bidsMap, priceType, bidResponses, RendererConst) { - if (!serverBid) return; - let errorMessage; - if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); - if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); - else { - const awaitingBids = bidsMap[serverBid.auid]; - if (awaitingBids) { - const sizeId = `${serverBid.w}x${serverBid.h}`; - if (awaitingBids[sizeId]) { - const slot = awaitingBids[sizeId][0]; - - const bid = slot.bids.shift(); - const bidResponse = { - requestId: bid.bidId, // bid.bidderRequestId, - bidderCode: spec.code, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid, // bid.bidId, - currency: 'EUR', - netRevenue: priceType !== 'gross', - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid - }; - if (serverBid.content_type === 'video' || (!serverBid.content_type && bid.mediaTypes && bid.mediaTypes.video)) { - bidResponse.vastXml = serverBid.adm; - bidResponse.mediaType = VIDEO; - bidResponse.adResponse = { - content: bidResponse.vastXml - }; - if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { - bidResponse.renderer = createRenderer(bidResponse, { - id: bid.bidId, - url: RENDERER_URL - }, RendererConst); - } - } else { - bidResponse.ad = serverBid.adm; - bidResponse.mediaType = BANNER; - } - - bidResponses.push(bidResponse); - - if (!slot.bids.length) { - slot.parents.forEach(({parent, key, uid}) => { - const index = parent[key].indexOf(slot); - if (index > -1) { - parent[key].splice(index, 1); - } - if (!parent[key].length) { - delete parent[key]; - if (!utils.getKeys(parent).length) { - delete bidsMap[uid]; - } - } - }); - } - } - } else { - errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; - } - } - if (errorMessage) { - utils.logError(errorMessage); - } -} - -function outstreamRender (bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - targetId: bid.adUnitCode, - adResponse: bid.adResponse - }); - }); -} - -function createRenderer (bid, rendererParams, RendererConst) { - const rendererInst = RendererConst.install({ - id: rendererParams.id, - url: rendererParams.url, - loaded: false - }); - - try { - rendererInst.setRender(outstreamRender); - } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); - } - - return rendererInst; -} - -export function resetUserSync() { - hasSynced = false; -} - -export function getSyncUrl() { - return SYNC_URL; -} - -registerBidder(spec); diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 74cd97ad019..d7c3315a814 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,7 +1,8 @@ -import * as utils from '../src/utils.js'; +import { logWarn, isStr, deepAccess, isArray, getBidIdParameter, deepSetValue, isEmpty, _each, convertTypes, parseUrl, mergeDeep, buildUrl, _map, logError, isFn, isPlainObject } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import { config } from '../src/config.js'; const GVLID = 24; export const storage = getStorageManager(GVLID); @@ -23,21 +24,22 @@ export const spec = { */ isBidRequestValid: function(bid) { if (!bid || !bid.params) { - utils.logWarn(BIDDER_CODE + ': Missing bid parameters'); + logWarn(BIDDER_CODE + ': Missing bid parameters'); return false; } - if (!utils.isStr(bid.params.site_id)) { - utils.logWarn(BIDDER_CODE + ': site_id must be specified as a string'); + if (!isStr(bid.params.site_id)) { + logWarn(BIDDER_CODE + ': site_id must be specified as a string'); return false; } if (isVideoRequest(bid)) { - if (!bid.params.mimes) { + const mimes = bid.params.mimes || deepAccess(bid, 'mediaTypes.video.mimes'); + if (!mimes) { // Give a warning but let it pass - utils.logWarn(BIDDER_CODE + ': mimes should be specified for videos'); - } else if (!utils.isArray(bid.params.mimes) || !bid.params.mimes.every(s => utils.isStr(s))) { - utils.logWarn(BIDDER_CODE + ': mimes must be an array of strings'); + logWarn(BIDDER_CODE + ': mimes should be specified for videos'); + } else if (!isArray(mimes) || !mimes.every(s => isStr(s))) { + logWarn(BIDDER_CODE + ': mimes must be an array of strings'); return false; } } @@ -61,10 +63,10 @@ export const spec = { let bidurl = URL; const conversantImps = validBidRequests.map(function(bid) { - const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); + const bidfloor = getBidFloor(bid); - siteId = utils.getBidIdParameter('site_id', bid.params) || siteId; - pubcidName = utils.getBidIdParameter('pubcid_name', bid.params) || pubcidName; + siteId = getBidIdParameter('site_id', bid.params) || siteId; + pubcidName = getBidIdParameter('pubcid_name', bid.params) || pubcidName; requestId = bid.auctionId; @@ -75,11 +77,14 @@ export const spec = { displaymanager: 'Prebid.js', displaymanagerver: '$prebid.version$' }; + if (bid.ortb2Imp) { + mergeDeep(imp, bid.ortb2Imp); + } copyOptProperty(bid.params.tag_id, imp, 'tagid'); if (isVideoRequest(bid)) { - const videoData = utils.deepAccess(bid, 'mediaTypes.video') || {}; + const videoData = deepAccess(bid, 'mediaTypes.video') || {}; const format = convertSizes(videoData.playerSize || bid.sizes); const video = {}; @@ -90,13 +95,13 @@ export const spec = { copyOptProperty(bid.params.position, video, 'pos'); copyOptProperty(bid.params.mimes || videoData.mimes, video, 'mimes'); - copyOptProperty(bid.params.maxduration, video, 'maxduration'); + copyOptProperty(bid.params.maxduration || videoData.maxduration, video, 'maxduration'); copyOptProperty(bid.params.protocols || videoData.protocols, video, 'protocols'); copyOptProperty(bid.params.api || videoData.api, video, 'api'); imp.video = video; } else { - const bannerData = utils.deepAccess(bid, 'mediaTypes.banner') || {}; + const bannerData = deepAccess(bid, 'mediaTypes.banner') || {}; const format = convertSizes(bannerData.sizes || bid.sizes); const banner = {format: format}; @@ -137,12 +142,12 @@ export const spec = { userExt.consent = bidderRequest.gdprConsent.consentString; if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { - utils.deepSetValue(payload, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + deepSetValue(payload, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); } } if (bidderRequest.uspConsent) { - utils.deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } } @@ -162,10 +167,13 @@ export const spec = { } // Only add the user object if it's not empty - if (!utils.isEmpty(userExt)) { + if (!isEmpty(userExt)) { payload.user = {ext: userExt}; } + const firstPartyData = config.getConfig('ortb2') || {}; + mergeDeep(payload, firstPartyData); + return { method: 'POST', url: bidurl, @@ -185,12 +193,12 @@ export const spec = { serverResponse = serverResponse.body; if (bidRequest && bidRequest.data && bidRequest.data.imp) { - utils._each(bidRequest.data.imp, imp => requestMap[imp.id] = imp); + _each(bidRequest.data.imp, imp => requestMap[imp.id] = imp); } - if (serverResponse && utils.isArray(serverResponse.seatbid)) { - utils._each(serverResponse.seatbid, function(bidList) { - utils._each(bidList.bid, function(conversantBid) { + if (serverResponse && isArray(serverResponse.seatbid)) { + _each(serverResponse.seatbid, function(bidList) { + _each(bidList.bid, function(conversantBid) { const responseCPM = parseFloat(conversantBid.price); if (responseCPM > 0.0 && conversantBid.impid) { const responseAd = conversantBid.adm || ''; @@ -242,11 +250,53 @@ export const spec = { * @return {Object} params bid params */ transformBidParams: function(params, isOpenRtb) { - return utils.convertTypes({ + return convertTypes({ 'site_id': 'string', 'secure': 'number', 'mobile': 'number' }, params); + }, + + /** + * Register User Sync. + */ + getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { + let params = {}; + const syncs = []; + + // Attaching GDPR Consent Params in UserSync url + if (gdprConsent) { + params.gdpr = (gdprConsent.gdprApplies) ? 1 : 0; + params.gdpr_consent = encodeURIComponent(gdprConsent.consentString || ''); + } + + // CCPA + if (uspConsent) { + params.us_privacy = encodeURIComponent(uspConsent); + } + + if (responses && responses.ext) { + const pixels = [{urls: responses.ext.fsyncs, type: 'iframe'}, {urls: responses.ext.psyncs, type: 'image'}] + .filter((entry) => { + return entry.urls && + ((entry.type === 'iframe' && syncOptions.iframeEnabled) || + (entry.type === 'image' && syncOptions.pixelEnabled)); + }) + .map((entry) => { + return entry.urls.map((endpoint) => { + let urlInfo = parseUrl(endpoint); + mergeDeep(urlInfo.search, params); + if (Object.keys(urlInfo.search).length === 0) { + delete urlInfo.search; // empty search object causes buildUrl to add a trailing ? to the url + } + return {type: entry.type, url: buildUrl(urlInfo)}; + }) + .reduce((x, y) => x.concat(y), []); + }) + .reduce((x, y) => x.concat(y), []); + syncs.push(...pixels); + } + return syncs; } }; @@ -290,7 +340,7 @@ function convertSizes(bidSizes) { if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { format = [{w: bidSizes[0], h: bidSizes[1]}]; } else { - format = utils._map(bidSizes, d => { return {w: d[0], h: d[1]}; }); + format = _map(bidSizes, d => { return {w: d[0], h: d[1]}; }); } } @@ -304,7 +354,7 @@ function convertSizes(bidSizes) { * @returns {boolean} True if it's a video bid */ function isVideoRequest(bid) { - return bid.mediaType === 'video' || !!utils.deepAccess(bid, 'mediaTypes.video'); + return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); } /** @@ -327,9 +377,10 @@ function copyOptProperty(src, dst, dstName) { function collectEids(bidRequests) { const request = bidRequests[0]; // bidRequests have the same userId object const eids = []; - if (utils.isArray(request.userIdAsEids) && request.userIdAsEids.length > 0) { + if (isArray(request.userIdAsEids) && request.userIdAsEids.length > 0) { // later following white-list can be converted to block-list if needed const requiredSourceValues = { + 'epsilon.com': 1, 'adserver.org': 1, 'liveramp.com': 1, 'criteo.com': 1, @@ -367,14 +418,38 @@ function readStoredValue(key) { } // deserialize JSON if needed - if (utils.isStr(storedValue) && storedValue.charAt(0) === '{') { + if (isStr(storedValue) && storedValue.charAt(0) === '{') { storedValue = JSON.parse(storedValue); } } catch (e) { - utils.logError(e); + logError(e); } return storedValue; } +/** + * Get the floor price from bid.params for backward compatibility. + * If not found, then check floor module. + * @param bid A valid bid object + * @returns {*|number} floor price + */ +function getBidFloor(bid) { + let floor = getBidIdParameter('bidfloor', bid.params); + + if (!floor && isFn(bid.getFloor)) { + const floorObj = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (isPlainObject(floorObj) && !isNaN(floorObj.floor) && floorObj.currency === 'USD') { + floor = floorObj.floor; + } + } + + return floor +} + registerBidder(spec); diff --git a/modules/conversantBidAdapter.md b/modules/conversantBidAdapter.md index fba793adad2..07d9abf918b 100644 --- a/modules/conversantBidAdapter.md +++ b/modules/conversantBidAdapter.md @@ -29,17 +29,17 @@ var adUnits = [ mediaTypes: { video: { context: 'instream', - playerSize: [640, 480] + playerSize: [640, 480], + api: [2], + protocols: [1, 2], + mimes: ['video/mp4'] } }, bids: [{ bidder: "conversant", params: { site_id: '108060', - api: [2], - protocols: [1, 2], - white_label_url: 'https://web.hb.ad.cpe.dotomi.com/s2s/header/24', - mimes: ['video/mp4'] + white_label_url: 'https://web.hb.ad.cpe.dotomi.com/s2s/header/24' } }] }]; diff --git a/modules/cosmosBidAdapter.js b/modules/cosmosBidAdapter.js deleted file mode 100644 index 73ee5c223b3..00000000000 --- a/modules/cosmosBidAdapter.js +++ /dev/null @@ -1,392 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; - -const BIDDER_CODE = 'cosmos'; -const BID_ENDPOINT = 'https://bid.cosmoshq.com/openrtb2/bids'; -const USER_SYNC_ENDPOINT = 'https://sync.cosmoshq.com/js/v1/usersync.html'; -const HTTP_POST = 'POST'; -const LOG_PREFIX = 'COSMOS: '; -const DEFAULT_CURRENCY = 'USD'; -const HTTPS = 'https:'; -const MEDIA_TYPES = 'mediaTypes'; -const MIMES = 'mimes'; -const DEFAULT_NET_REVENUE = false; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], - - /** - * generate UUID - **/ - _createUUID: function () { - return ('' + new Date().getTime()); - }, - - /** - * copy object if not null - **/ - _copyObject: function (src, dst) { - if (src) { - // copy complete object - Object.keys(src).forEach(param => dst[param] = src[param]); - } - }, - - /** - * parse object - **/ - _parse: function (rawPayload) { - try { - if (rawPayload) { - return JSON.parse(rawPayload); - } - } catch (ex) { - utils.logError(LOG_PREFIX, 'Exception: ', ex); - } - return null; - }, - - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - **/ - isBidRequestValid: function (bid) { - if (!bid || !bid.params) { - utils.logError(LOG_PREFIX, 'nil/empty bid object'); - return false; - } - - if (!utils.isEmpty(bid.params.publisherId) || - !utils.isNumber(bid.params.publisherId)) { - utils.logError(LOG_PREFIX, 'publisherId is mandatory and must be numeric. Ad Unit: ', JSON.stringify(bid)); - return false; - } - // video bid request validation - if (bid.hasOwnProperty(MEDIA_TYPES) && bid.mediaTypes.hasOwnProperty(VIDEO)) { - if (!bid.mediaTypes.video.hasOwnProperty(MIMES) || - !utils.isArray(bid.mediaTypes.video.mimes) || - bid.mediaTypes.video.mimes.length === 0) { - utils.logError(LOG_PREFIX, 'mimes are mandatory for video bid request. Ad Unit: ', JSON.stringify(bid)); - return false; - } - } - - return true; - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - **/ - buildRequests: function (validBidRequests, bidderRequest) { - if (validBidRequests.length === 0) { - return []; - } - - var refererInfo; - if (bidderRequest && bidderRequest.refererInfo) { - refererInfo = bidderRequest.refererInfo; - } - - let clonedBidRequests = utils.deepClone(validBidRequests); - return clonedBidRequests.map(bidRequest => { - const oRequest = spec._createRequest(bidRequest, refererInfo); - if (oRequest) { - spec._setGDPRParams(bidderRequest, oRequest); - return { - method: HTTP_POST, - url: BID_ENDPOINT, - data: JSON.stringify(oRequest) - }; - } - }); - }, - - /** - * 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, request) { - let response = serverResponse.body; - var bidResponses = []; - try { - if (response.seatbid) { - var currency = response.cur ? response.cur : DEFAULT_CURRENCY; - response.seatbid.forEach(seatbid => { - var bids = seatbid.bid ? seatbid.bid : []; - bids.forEach(bid => { - var bidResponse = { - requestId: bid.impid, - cpm: (parseFloat(bid.price) || 0).toFixed(2), - width: bid.w, - height: bid.h, - creativeId: bid.crid, - currency: currency, - netRevenue: DEFAULT_NET_REVENUE, - ttl: 300 - }; - if (bid.dealid) { - bidResponse.dealId = bid.dealid; - } - - var req = spec._parse(request.data); - if (req.imp && req.imp.length > 0) { - req.imp.forEach(impr => { - if (impr.id === bid.impid) { - if (impr.banner) { - bidResponse.ad = bid.adm; - bidResponse.mediaType = BANNER; - } else { - bidResponse.width = bid.hasOwnProperty('w') ? bid.w : impr.video.w; - bidResponse.height = bid.hasOwnProperty('h') ? bid.h : impr.video.h; - bidResponse.vastXml = bid.adm; - bidResponse.mediaType = VIDEO; - } - } - }); - } - bidResponses.push(bidResponse); - }); - }); - } - } catch (ex) { - utils.logError(LOG_PREFIX, 'Exception: ', ex); - } - 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. - * @return {UserSync[]} The user syncs which should be dropped. - **/ - getUserSyncs: function (syncOptions, serverResponses) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: USER_SYNC_ENDPOINT - }]; - } else { - utils.logWarn(LOG_PREFIX + 'Please enable iframe based user sync.'); - } - }, - - /** - * create IAB standard OpenRTB bid request - **/ - _createRequest: function (bidRequests, refererInfo) { - var oRequest = {}; - try { - oRequest = { - id: spec._createUUID(), - imp: spec._createImpressions(bidRequests), - user: {}, - ext: {} - }; - var site = spec._createSite(bidRequests, refererInfo); - var app = spec._createApp(bidRequests); - var device = spec._createDevice(bidRequests); - if (app) { - oRequest.app = app; - } - if (site) { - oRequest.site = site; - } - if (device) { - oRequest.device = device; - } - } catch (ex) { - utils.logError(LOG_PREFIX, 'Exception: ', ex); - oRequest = null; - } - return oRequest; - }, - - /** - * create impression array objects - **/ - _createImpressions: function (request) { - var impressions = []; - var impression = spec._creatImpression(request); - if (impression) { - impressions.push(impression); - } - return impressions; - }, - - /** - * create impression (single) object - **/ - _creatImpression: function (request) { - if (!request.hasOwnProperty(MEDIA_TYPES)) { - return undefined; - } - - var params = request && request.params ? request.params : null; - var impression = { - id: request.bidId ? request.bidId : spec._createUUID(), - secure: window.location.protocol === HTTPS ? 1 : 0, - bidfloorcur: request.params.currency ? request.params.currency : DEFAULT_CURRENCY - }; - if (params.bidFloor) { - impression.bidfloor = params.bidFloor; - } - - if (params.tagId) { - impression.tagid = params.tagId.toString(); - } - - var banner; - var video; - var mediaType; - for (mediaType in request.mediaTypes) { - switch (mediaType) { - case BANNER: - banner = spec._createBanner(request); - if (banner) { - impression.banner = banner; - } - break; - case VIDEO: - video = spec._createVideo(request); - if (video) { - impression.video = video; - } - break; - } - } - - return impression.hasOwnProperty(BANNER) || - impression.hasOwnProperty(VIDEO) ? impression : undefined; - }, - - /** - * create the banner object - **/ - _createBanner: function (request) { - if (utils.deepAccess(request, 'mediaTypes.banner')) { - var banner = {}; - var sizes = request.mediaTypes.banner.sizes; - if (sizes && utils.isArray(sizes) && sizes.length > 0) { - var format = []; - banner.w = sizes[0][0]; - banner.h = sizes[0][1]; - sizes.forEach(size => { - format.push({ - w: size[0], - h: size[1] - }); - }); - banner.format = format; - } - - spec._copyObject(request.mediaTypes.banner, banner); - spec._copyObject(request.params.banner, banner); - return banner; - } - return undefined; - }, - - /** - * create video object - **/ - _createVideo: function (request) { - if (utils.deepAccess(request, 'mediaTypes.video')) { - var video = {}; - var sizes = request.mediaTypes.video.playerSize; - if (sizes && utils.isArray(sizes) && sizes.length > 1) { - video.w = sizes[0]; - video.h = sizes[1]; - } - spec._copyObject(request.mediaTypes.video, video); - spec._copyObject(request.params.video, video); - return video; - } - return undefined; - }, - - /** - * create site object - **/ - _createSite: function (request, refererInfo) { - var rSite = request.params.site; - if (rSite || !request.params.app) { - var site = {}; - spec._copyObject(rSite, site); - - if (refererInfo) { - if (refererInfo.referer) { - site.ref = encodeURIComponent(refererInfo.referer); - } - if (utils.isArray(refererInfo.stack) && refererInfo.stack.length > 0) { - site.page = encodeURIComponent(refererInfo.stack[0]); - let anchrTag = document.createElement('a'); - anchrTag.href = site.page; - site.domain = anchrTag.hostname; - } - } - - // override publisher object - site.publisher = { - id: request.params.publisherId.toString() - }; - return site; - } - return undefined; - }, - - /** - * create app object - **/ - _createApp: function (request) { - var rApp = request.params.app; - if (rApp) { - var app = {}; - spec._copyObject(rApp, app); - // override publisher object - app.publisher = { - id: request.params.publisherId.toString() - }; - return app; - } - return undefined; - }, - - /** - * create device obejct - **/ - _createDevice: function (request) { - var device = {}; - var rDevice = request.params.device; - spec._copyObject(rDevice, device); - device.dnt = utils.getDNT() ? 1 : 0; - device.ua = navigator.userAgent; - device.language = (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage); - device.w = (window.screen.width || window.innerWidth); - device.h = (window.screen.height || window.innerHeigh); - return device; - }, - - /** - * set GDPR parameters - **/ - _setGDPRParams: function (bidderRequest, oRequest) { - if (!bidderRequest || !bidderRequest.gdprConsent) { - return; - } - - oRequest.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; - oRequest.user = { ext: { consent: bidderRequest.gdprConsent.consentString } }; - }, - -} -registerBidder(spec); diff --git a/modules/cpmstarBidAdapter.js b/modules/cpmstarBidAdapter.js old mode 100644 new mode 100755 index 61ccc0e786b..75a7007ee36 --- a/modules/cpmstarBidAdapter.js +++ b/modules/cpmstarBidAdapter.js @@ -1,5 +1,4 @@ - -import * as utils from '../src/utils.js'; +import { deepAccess, getBidIdParameter, logWarn, logError } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -26,11 +25,11 @@ export const spec = { getMediaType: function (bidRequest) { if (bidRequest == null) return BANNER; - return !utils.deepAccess(bidRequest, 'mediaTypes.video') ? BANNER : VIDEO; + return !deepAccess(bidRequest, 'mediaTypes.video') ? BANNER : VIDEO; }, getPlayerSize: function (bidRequest) { - var playerSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + var playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); if (playerSize == null) return [640, 440]; if (playerSize[0] != null) playerSize = playerSize[0]; if (playerSize == null || playerSize[0] == null || playerSize[1] == null) return [640, 440]; @@ -48,13 +47,13 @@ export const spec = { for (var i = 0; i < validBidRequests.length; i++) { var bidRequest = validBidRequests[i]; var referer = encodeURIComponent(bidderRequest.refererInfo.referer); - var e = utils.getBidIdParameter('endpoint', bidRequest.params); + var e = getBidIdParameter('endpoint', bidRequest.params); var ENDPOINT = e == 'dev' ? ENDPOINT_DEV : e == 'staging' ? ENDPOINT_STAGING : ENDPOINT_PRODUCTION; var mediaType = spec.getMediaType(bidRequest); var playerSize = spec.getPlayerSize(bidRequest); var videoArgs = '&fv=0' + (playerSize ? ('&w=' + playerSize[0] + '&h=' + playerSize[1]) : ''); var url = ENDPOINT + '?media=' + mediaType + (mediaType == VIDEO ? videoArgs : '') + - '&json=c_b&mv=1&poolid=' + utils.getBidIdParameter('placementId', bidRequest.params) + + '&json=c_b&mv=1&poolid=' + getBidIdParameter('placementId', bidRequest.params) + '&reachedTop=' + encodeURIComponent(bidderRequest.refererInfo.reachedTop) + '&requestid=' + bidRequest.bidId + '&referer=' + encodeURIComponent(referer); @@ -117,13 +116,13 @@ export const spec = { var raw = serverResponse.body[i]; var rawBid = raw.creatives[0]; if (!rawBid) { - utils.logWarn('cpmstarBidAdapter: server response failed check'); + logWarn('cpmstarBidAdapter: server response failed check'); return; } var cpm = (parseFloat(rawBid.cpm) || 0); if (!cpm) { - utils.logWarn('cpmstarBidAdapter: server response failed check. Missing cpm') + logWarn('cpmstarBidAdapter: server response failed check. Missing cpm') return; } @@ -136,6 +135,9 @@ export const spec = { netRevenue: rawBid.netRevenue ? rawBid.netRevenue : true, ttl: rawBid.ttl ? rawBid.ttl : DEFAULT_TTL, creativeId: rawBid.creativeid || 0, + meta: { + advertiserDomains: rawBid.adomain ? rawBid.adomain : [] + } }; if (rawBid.hasOwnProperty('dealId')) { @@ -153,7 +155,7 @@ export const spec = { bidResponse.mediaType = VIDEO; bidResponse.vastXml = rawBid.creativemacros.HTML5VID_VASTSTRING; } else { - return utils.logError('bad response', rawBid); + return logError('bad response', rawBid); } bidResponses.push(bidResponse); diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 0124f96a107..812ec53d686 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -1,10 +1,11 @@ -import * as utils from '../src/utils.js'; +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 {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'craft'; const URL_BASE = 'https://gacraft.jp/prebid-v3'; @@ -67,7 +68,7 @@ export const spec = { if (serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } - utils.logError(errorMessage); + logError(errorMessage); return bids; } if (serverResponse.tags) { @@ -89,17 +90,17 @@ export const spec = { }, transformBidParams: function(params, isOpenRtb) { - params = utils.convertTypes({ + params = convertTypes({ 'sitekey': 'string', 'placementId': 'string', - 'keywords': utils.transformBidderParamKeywords + 'keywords': transformBidderParamKeywords }, params); if (isOpenRtb) { if (isPopulatedArray(params.keywords)) { params.keywords.forEach(deleteValues); } Object.keys(params).forEach(paramKey => { - let convertedKey = utils.convertCamelToUnderscore(paramKey); + let convertedKey = convertCamelToUnderscore(paramKey); if (convertedKey !== paramKey) { params[convertedKey] = params[paramKey]; delete params[paramKey]; @@ -110,14 +111,15 @@ export const spec = { }, onBidWon: function(bid) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', bid._prebidWon); - xhr.send(); + ajax(bid._prebidWon, null, null, { + method: 'POST', + contentType: 'application/json' + }); } }; function isPopulatedArray(arr) { - return !!(utils.isArray(arr) && arr.length > 0); + return !!(isArray(arr) && arr.length > 0); } function deleteValues(keyPairObj) { @@ -130,7 +132,7 @@ function hasPurpose1Consent(bidderRequest) { let result = true; if (bidderRequest && bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { - result = !!(utils.deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); + result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); } } return result; @@ -155,7 +157,7 @@ function formatRequest(payload, bidderRequest) { } function newBid(serverBid, rtbBid, bidderRequest) { - const bidRequest = utils.getBidRequest(serverBid.uuid, [bidderRequest]); + const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); const bid = { requestId: serverBid.uuid, cpm: rtbBid.cpm, @@ -190,8 +192,8 @@ function bidToTag(bid) { tag.primary_size = tag.sizes[0]; tag.ad_types = []; tag.uuid = bid.bidId; - if (!utils.isEmpty(bid.params.keywords)) { - let keywords = utils.transformBidderParamKeywords(bid.params.keywords); + if (!isEmpty(bid.params.keywords)) { + let keywords = transformBidderParamKeywords(bid.params.keywords); if (keywords.length > 0) { keywords.forEach(deleteValues); } diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 41cbb0670c8..0285ab6be5b 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,14 +1,14 @@ +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 * as utils from '../src/utils.js'; import find from 'core-js-pure/features/array/find.js'; -import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; +import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2 import { getStorageManager } from '../src/storageManager.js'; const GVLID = 91; -export const ADAPTER_VERSION = 33; +export const ADAPTER_VERSION = 34; const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; const PROFILE_ID_INLINE = 207; @@ -16,9 +16,18 @@ export const PROFILE_ID_PUBLISHERTAG = 185; const storage = getStorageManager(GVLID); const LOG_PREFIX = 'Criteo: '; -// Unminified source code can be found in: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js -const PUBLISHER_TAG_URL = 'https://static.criteo.net/js/ld/publishertag.prebid.js'; - +/* + If you don't want to use the FastBid adapter feature, you can lighten criteoBidAdapter size by : + 1. commenting the tryGetCriteoFastBid function inner content (see ref#1) + 2. removing the line 'verify' function import line (see ref#2) + + Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js +*/ +const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; +export const FAST_BID_VERSION_CURRENT = 117; +const FAST_BID_VERSION_LATEST = 'latest'; +const FAST_BID_VERSION_NONE = 'none'; +const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; const FAST_BID_PUBKEY_E = 65537; const FAST_BID_PUBKEY_N = 'ztQYwCE5BU7T9CDM5he6rKoabstXRmkzx54zFPZkWbK530dwtLBDeaWBMxHBUT55CYyboR/EZ4efghPi3CoNGfGWezpjko9P6p2EwGArtHEeS4slhu/SpSIFMjG6fdrpRoNuIAMhq1Z+Pr/+HOd1pThFKeGFr2/NhtAg+TXAzaU='; @@ -65,15 +74,18 @@ export const spec = { }); // If publisher tag not already loaded try to get it from fast bid - if (!publisherTagAvailable()) { + const fastBidVersion = config.getConfig('criteo.fastBidVersion'); + const canLoadPublisherTag = canFastBid(fastBidVersion); + if (!publisherTagAvailable() && canLoadPublisherTag) { window.Criteo = window.Criteo || {}; window.Criteo.usePrebidEvents = false; tryGetCriteoFastBid(); + const fastBidUrl = getFastBidUrl(fastBidVersion); // Reload the PublisherTag after the timeout to ensure FastBid is up-to-date and tracking done properly setTimeout(() => { - loadExternalScript(PUBLISHER_TAG_URL, BIDDER_CODE); + loadExternalScript(fastBidUrl, BIDDER_CODE); }, bidderRequest.timeout); } @@ -111,22 +123,25 @@ export const spec = { const bids = []; - if (body && body.slots && utils.isArray(body.slots)) { + if (body && body.slots && isArray(body.slots)) { body.slots.forEach(slot => { const bidRequest = find(request.bidRequests, b => b.adUnitCode === slot.impid && (!b.params.zoneId || parseInt(b.params.zoneId) === slot.zoneid)); const bidId = bidRequest.bidId; const bid = { requestId: bidId, - adId: slot.bidId || utils.getUniqueIdentifierStr(), + adId: slot.bidId || getUniqueIdentifierStr(), cpm: slot.cpm, currency: slot.currency, netRevenue: true, ttl: slot.ttl || 60, - creativeId: bidId, + creativeId: slot.creativecode, width: slot.width, height: slot.height, dealId: slot.dealCode, }; + if (slot.adomain) { + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: slot.adomain }); + } if (slot.native) { if (bidRequest.params.nativeCallback) { bid.ad = createNativeAd(bidId, slot.native, bidRequest.params.nativeCallback); @@ -204,7 +219,7 @@ function buildContext(bidRequests, bidderRequest) { if (bidderRequest && bidderRequest.refererInfo) { referrer = bidderRequest.refererInfo.referer; } - const queryString = utils.parseUrl(referrer).search; + const queryString = parseUrl(referrer).search; const context = { url: referrer, @@ -266,6 +281,7 @@ function checkNativeSendId(bidRequest) { */ function buildCdbRequest(context, bidRequests, bidderRequest) { let networkId; + let schain; const request = { publisher: { url: context.url, @@ -273,6 +289,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { }, slots: bidRequests.map(bidRequest => { networkId = bidRequest.params.networkId || networkId; + schain = bidRequest.schain || schain; const slot = { impid: bidRequest.adUnitCode, transactionid: bidRequest.transactionId, @@ -281,7 +298,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.params.zoneId) { slot.zoneid = bidRequest.params.zoneId; } - if (utils.deepAccess(bidRequest, 'ortb2Imp.ext')) { + if (deepAccess(bidRequest, 'ortb2Imp.ext')) { slot.ext = bidRequest.ortb2Imp.ext; } if (bidRequest.params.ext) { @@ -290,10 +307,10 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.params.publisherSubId) { slot.publishersubid = bidRequest.params.publisherSubId; } - if (bidRequest.params.nativeCallback || utils.deepAccess(bidRequest, `mediaTypes.${NATIVE}`)) { + if (bidRequest.params.nativeCallback || deepAccess(bidRequest, `mediaTypes.${NATIVE}`)) { slot.native = true; if (!checkNativeSendId(bidRequest)) { - utils.logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); + 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); } else { @@ -301,18 +318,25 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { } if (hasVideoMediaType(bidRequest)) { const video = { - playersizes: parseSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), + playersizes: parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), mimes: bidRequest.mediaTypes.video.mimes, protocols: bidRequest.mediaTypes.video.protocols, maxduration: bidRequest.mediaTypes.video.maxduration, - api: bidRequest.mediaTypes.video.api + api: bidRequest.mediaTypes.video.api, + skip: bidRequest.mediaTypes.video.skip, + placement: bidRequest.mediaTypes.video.placement, + minduration: bidRequest.mediaTypes.video.minduration, + playbackmethod: bidRequest.mediaTypes.video.playbackmethod, + startdelay: bidRequest.mediaTypes.video.startdelay }; - - video.skip = bidRequest.params.video.skip; - video.placement = bidRequest.params.video.placement; - video.minduration = bidRequest.params.video.minduration; - video.playbackmethod = bidRequest.params.video.playbackmethod; - video.startdelay = bidRequest.params.video.startdelay; + const paramsVideo = bidRequest.params.video; + if (paramsVideo !== undefined) { + video.skip = video.skip || paramsVideo.skip || 0; + video.placement = video.placement || paramsVideo.placement; + video.minduration = video.minduration || paramsVideo.minduration; + video.playbackmethod = video.playbackmethod || paramsVideo.playbackmethod; + video.startdelay = video.startdelay || paramsVideo.startdelay || 0; + } slot.video = video; } @@ -322,6 +346,13 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (networkId) { request.publisher.networkid = networkId; } + if (schain) { + request.source = { + ext: { + schain: schain + } + } + }; request.user = { ext: bidderRequest.userExt }; @@ -345,7 +376,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { } function retrieveBannerSizes(bidRequest) { - return utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; + return deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; } function parseSizes(sizes, parser) { @@ -367,38 +398,27 @@ function parseNativeSize(size) { } function hasVideoMediaType(bidRequest) { - if (utils.deepAccess(bidRequest, 'params.video') === undefined) { - return false; - } - return utils.deepAccess(bidRequest, 'mediaTypes.video') !== undefined; + return deepAccess(bidRequest, 'mediaTypes.video') !== undefined; } function hasValidVideoMediaType(bidRequest) { let isValid = true; - var requiredMediaTypesParams = ['mimes', 'playerSize', 'maxduration', 'protocols', 'api']; + var requiredMediaTypesParams = ['mimes', 'playerSize', 'maxduration', 'protocols', 'api', 'skip', 'placement', 'playbackmethod']; requiredMediaTypesParams.forEach(function(param) { - if (utils.deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined) { - isValid = false; - utils.logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required'); - } - }); - - var requiredParams = ['skip', 'placement', 'playbackmethod']; - - requiredParams.forEach(function(param) { - if (utils.deepAccess(bidRequest, 'params.video.' + param) === undefined) { + if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) { isValid = false; - utils.logError('Criteo Bid Adapter: params.video.' + param + ' is required'); + logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required'); } }); if (isValid) { + const videoPlacement = bidRequest.mediaTypes.video.placement || bidRequest.params.video.placement; // We do not support long form for now, also we have to check that context & placement are consistent - if (bidRequest.mediaTypes.video.context == 'instream' && bidRequest.params.video.placement === 1) { + if (bidRequest.mediaTypes.video.context == 'instream' && videoPlacement === 1) { return true; - } else if (bidRequest.mediaTypes.video.context == 'outstream' && bidRequest.params.video.placement !== 1) { + } else if (bidRequest.mediaTypes.video.context == 'outstream' && videoPlacement !== 1) { return true; } } @@ -454,7 +474,29 @@ for (var i = 0; i < 10; ++i) { `; } +export function canFastBid(fastBidVersion) { + return fastBidVersion !== FAST_BID_VERSION_NONE; +} + +export function getFastBidUrl(fastBidVersion) { + let version; + if (fastBidVersion === FAST_BID_VERSION_LATEST) { + version = ''; + } else if (fastBidVersion) { + let majorVersion = String(fastBidVersion).split('.')[0]; + if (majorVersion < 102) { + logWarn('Specifying a Fastbid version which is not supporting version selection.') + } + version = '.' + fastBidVersion; + } else { + version = '.' + FAST_BID_VERSION_CURRENT; + } + + return PUBLISHER_TAG_URL_TEMPLATE.replace(FAST_BID_VERSION_PLACEHOLDER, version); +} + export function tryGetCriteoFastBid() { + // begin ref#1 try { const fastBidStorageKey = 'criteo_fast_bid'; const hashPrefix = '// Hash: '; @@ -466,7 +508,7 @@ export function tryGetCriteoFastBid() { const firstLine = fastBidFromStorage.substr(0, firstLineEndPosition).trim(); if (firstLine.substr(0, hashPrefix.length) !== hashPrefix) { - utils.logWarn('No hash found in FastBid'); + logWarn('No hash found in FastBid'); storage.removeDataFromLocalStorage(fastBidStorageKey); } else { // Remove the hash part from the locally stored value @@ -474,10 +516,10 @@ export function tryGetCriteoFastBid() { const publisherTag = fastBidFromStorage.substr(firstLineEndPosition + 1); if (verify(publisherTag, publisherTagHash, FAST_BID_PUBKEY_N, FAST_BID_PUBKEY_E)) { - utils.logInfo('Using Criteo FastBid'); + logInfo('Using Criteo FastBid'); eval(publisherTag); // eslint-disable-line no-eval } else { - utils.logWarn('Invalid Criteo FastBid found'); + logWarn('Invalid Criteo FastBid found'); storage.removeDataFromLocalStorage(fastBidStorageKey); } } @@ -485,6 +527,7 @@ export function tryGetCriteoFastBid() { } catch (e) { // Unable to get fast bid } + // end ref#1 } registerBidder(spec); diff --git a/modules/criteoBidAdapter.md b/modules/criteoBidAdapter.md index 68599f05434..6a165978f3b 100644 --- a/modules/criteoBidAdapter.md +++ b/modules/criteoBidAdapter.md @@ -31,7 +31,8 @@ Set the "ceh" property to provides the user's hashed email if available ``` pbjs.setConfig({ criteo: { - ceh: 'hashed mail' + ceh: 'hashed mail', + fastBidVersion: "none"|"latest"| } }); -``` \ No newline at end of file +``` diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index ac26d34d529..ecf7b3aaac4 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -5,9 +5,9 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js' -import * as ajax from '../src/ajax.js' -import { getRefererInfo } from '../src/refererDetection.js' +import { timestamp, parseUrl, triggerPixel, logError } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { getRefererInfo } from '../src/refererDetection.js'; import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -20,10 +20,10 @@ const bundleStorageKey = 'cto_bundle'; const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000; const pastDateString = new Date(0).toString(); -const expirationString = new Date(utils.timestamp() + cookiesMaxAge).toString(); +const expirationString = new Date(timestamp() + cookiesMaxAge).toString(); function extractProtocolHost (url, returnOnlyHost = false) { - const parsedUrl = utils.parseUrl(url, {noDecodeWholeURL: true}) + const parsedUrl = parseUrl(url, {noDecodeWholeURL: true}) return returnOnlyHost ? `${parsedUrl.hostname}` : `${parsedUrl.protocol}://${parsedUrl.hostname}${parsedUrl.port ? ':' + parsedUrl.port : ''}/`; @@ -33,15 +33,37 @@ function getFromAllStorages(key) { return storage.getCookie(key) || storage.getDataFromLocalStorage(key); } -function saveOnAllStorages(key, value) { +function saveOnAllStorages(key, value, hostname) { if (key && value) { - storage.setCookie(key, value, expirationString); storage.setDataInLocalStorage(key, value); + setCookieOnAllDomains(key, value, expirationString, hostname, true); } } -function deleteFromAllStorages(key) { - storage.setCookie(key, '', pastDateString); +function setCookieOnAllDomains(key, value, expiration, hostname, stopOnSuccess) { + const subDomains = hostname.split('.'); + for (let i = 0; i < subDomains.length; ++i) { + // Try to write the cookie on this subdomain (we want it to be stored only on the TLD+1) + const domain = subDomains.slice(subDomains.length - i - 1, subDomains.length).join('.'); + + try { + storage.setCookie(key, value, expiration, null, '.' + domain); + + if (stopOnSuccess) { + // Try to read the cookie to check if we wrote it + const ck = storage.getCookie(key); + if (ck && ck === value) { + break; + } + } + } catch (error) { + + } + } +} + +function deleteFromAllStorages(key, hostname) { + setCookieOnAllDomains(key, '', pastDateString, hostname, true); storage.removeDataFromLocalStorage(key); } @@ -65,7 +87,7 @@ function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isL return url; } -function callCriteoUserSync(parsedCriteoData, gdprString) { +function callCriteoUserSync(parsedCriteoData, gdprString, callback) { const cw = storage.cookiesAreEnabled(); const lsw = storage.localStorageIsEnabled(); const topUrl = extractProtocolHost(getRefererInfo().referer); @@ -82,26 +104,32 @@ function callCriteoUserSync(parsedCriteoData, gdprString) { gdprString ); - ajax.ajaxBuilder()( - url, - response => { + const callbacks = { + success: response => { const jsonResponse = JSON.parse(response); - if (jsonResponse.bidId) { - saveOnAllStorages(bididStorageKey, jsonResponse.bidId); - } else { - deleteFromAllStorages(bididStorageKey); - } - if (jsonResponse.acwsUrl) { const urlsToCall = typeof jsonResponse.acwsUrl === 'string' ? [jsonResponse.acwsUrl] : jsonResponse.acwsUrl; - urlsToCall.forEach(url => utils.triggerPixel(url)); + urlsToCall.forEach(url => triggerPixel(url)); } else if (jsonResponse.bundle) { - saveOnAllStorages(bundleStorageKey, jsonResponse.bundle); + saveOnAllStorages(bundleStorageKey, jsonResponse.bundle, domain); + } + + if (jsonResponse.bidId) { + saveOnAllStorages(bididStorageKey, jsonResponse.bidId, domain); + const criteoId = { criteoId: jsonResponse.bidId }; + callback(criteoId); + } else { + deleteFromAllStorages(bididStorageKey, domain); + callback(); } }, - undefined, - { method: 'GET', contentType: 'application/json', withCredentials: true } - ); + error: error => { + logError(`criteoIdSystem: unable to sync user id`, error); + callback(); + } + }; + + ajax(url, callbacks, undefined, { method: 'GET', contentType: 'application/json', withCredentials: true }); } /** @type {Submodule} */ @@ -132,9 +160,13 @@ export const criteoIdSubmodule = { const gdprConsentString = hasGdprData ? consentData.consentString : undefined; let localData = getCriteoDataFromAllStorages(); - callCriteoUserSync(localData, gdprConsentString); - return { id: localData.bidId ? { criteoId: localData.bidId } : undefined } + const result = (callback) => callCriteoUserSync(localData, gdprConsentString, callback); + + return { + id: localData.bidId ? { criteoId: localData.bidId } : undefined, + callback: result + } } }; diff --git a/modules/currency.js b/modules/currency.js index 0464d9b5cdb..a59a9880af1 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -1,8 +1,8 @@ +import { logInfo, logWarn, logError, logMessage } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { createBid } from '../src/bidfactory.js'; -import { STATUS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; -import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; import { getHook } from '../src/hook.js'; @@ -20,6 +20,25 @@ export var currencyRates = {}; var bidderCurrencyDefault = {}; var defaultRates; +export const ready = (() => { + let isDone, resolver, promise; + function reset() { + isDone = false; + resolver = null; + promise = new Promise((resolve) => { + resolver = resolve; + if (isDone) resolve(); + }) + } + function done() { + isDone = true; + if (resolver != null) { resolver() } + } + reset(); + + return {done, reset, promise: () => promise} +})(); + /** * Configuration function for currency * @param {string} [config.adServerCurrency = 'USD'] @@ -70,11 +89,11 @@ export function setConfig(config) { } if (typeof config.adServerCurrency === 'string') { - utils.logInfo('enabling currency support', arguments); + logInfo('enabling currency support', arguments); adServerCurrency = config.adServerCurrency; if (config.conversionRateFile) { - utils.logInfo('currency using override conversionRateFile:', config.conversionRateFile); + logInfo('currency using override conversionRateFile:', config.conversionRateFile); url = config.conversionRateFile; } @@ -99,7 +118,7 @@ export function setConfig(config) { initCurrency(url); } else { // currency support is disabled, setting defaults - utils.logInfo('disabling currency support'); + logInfo('disabling currency support'); resetCurrency(); } if (typeof config.bidderCurrencyDefault === 'object') { @@ -110,10 +129,10 @@ config.getConfig('currency', config => setConfig(config.currency)); function errorSettingsRates(msg) { if (defaultRates) { - utils.logWarn(msg); - utils.logWarn('Currency failed loading rates, falling back to currency.defaultRates'); + logWarn(msg); + logWarn('Currency failed loading rates, falling back to currency.defaultRates'); } else { - utils.logError(msg); + logError(msg); } } @@ -121,7 +140,7 @@ function initCurrency(url) { conversionCache = {}; currencySupportEnabled = true; - utils.logInfo('Installing addBidResponse decorator for currency module', arguments); + logInfo('Installing addBidResponse decorator for currency module', arguments); // Adding conversion function to prebid global for external module and on page use getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency); @@ -135,21 +154,25 @@ function initCurrency(url) { success: function (response) { try { currencyRates = JSON.parse(response); - utils.logInfo('currencyRates set to ' + JSON.stringify(currencyRates)); + logInfo('currencyRates set to ' + JSON.stringify(currencyRates)); currencyRatesLoaded = true; processBidResponseQueue(); + ready.done(); } catch (e) { errorSettingsRates('Failed to parse currencyRates response: ' + response); } }, - error: errorSettingsRates + error: function (...args) { + errorSettingsRates(...args); + ready.done(); + } } ); } } function resetCurrency() { - utils.logInfo('Uninstalling addBidResponse decorator for currency module', arguments); + logInfo('Uninstalling addBidResponse decorator for currency module', arguments); getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); delete getGlobal().convertCurrency; @@ -172,7 +195,7 @@ export function addBidResponseHook(fn, adUnitCode, bid) { if (bidderCurrencyDefault[bidder]) { let currencyDefault = bidderCurrencyDefault[bidder]; if (bid.currency && currencyDefault !== bid.currency) { - utils.logWarn(`Currency default '${bidder}: ${currencyDefault}' ignored. adapter specified '${bid.currency}'`); + logWarn(`Currency default '${bidder}: ${currencyDefault}' ignored. adapter specified '${bid.currency}'`); } else { bid.currency = currencyDefault; } @@ -180,7 +203,7 @@ export function addBidResponseHook(fn, adUnitCode, bid) { // default to USD if currency not set if (!bid.currency) { - utils.logWarn('Currency not specified on bid. Defaulted to "USD"'); + logWarn('Currency not specified on bid. Defaulted to "USD"'); bid.currency = 'USD'; } @@ -197,6 +220,8 @@ export function addBidResponseHook(fn, adUnitCode, bid) { bidResponseQueue.push(wrapFunction(fn, this, [adUnitCode, bid])); if (!currencySupportEnabled || currencyRatesLoaded) { processBidResponseQueue(); + } else { + fn.bail(ready.promise()); } } @@ -218,11 +243,8 @@ function wrapFunction(fn, context, params) { bid.currency = adServerCurrency; } } catch (e) { - utils.logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e); - params[1] = createBid(STATUS.NO_BID, { - bidder: bid.bidderCode || bid.bidder, - bidId: bid.requestId - }); + logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e); + params[1] = createBid(CONSTANTS.STATUS.NO_BID, bid.getIdentifiers()); } } return fn.apply(context, params); @@ -235,7 +257,7 @@ function getCurrencyConversion(fromCurrency, toCurrency = adServerCurrency) { let cacheKey = `${fromCurrency}->${toCurrency}`; if (cacheKey in conversionCache) { conversionRate = conversionCache[cacheKey]; - utils.logMessage('Using conversionCache value ' + conversionRate + ' for ' + cacheKey); + logMessage('Using conversionCache value ' + conversionRate + ' for ' + cacheKey); } else if (currencySupportEnabled === false) { if (fromCurrency === 'USD') { conversionRate = 1; @@ -253,7 +275,7 @@ function getCurrencyConversion(fromCurrency, toCurrency = adServerCurrency) { throw new Error('Specified adServerCurrency in config \'' + toCurrency + '\' not found in the currency rates file'); } conversionRate = rates[toCurrency]; - utils.logInfo('getCurrencyConversion using direct ' + fromCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate); + logInfo('getCurrencyConversion using direct ' + fromCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate); } else if (toCurrency in currencyRates.conversions) { // using reciprocal of conversion rate from toCurrency to fromCurrency rates = currencyRates.conversions[toCurrency]; @@ -262,7 +284,7 @@ function getCurrencyConversion(fromCurrency, toCurrency = adServerCurrency) { throw new Error('Specified fromCurrency \'' + fromCurrency + '\' not found in the currency rates file'); } conversionRate = roundFloat(1 / rates[fromCurrency], CURRENCY_RATE_PRECISION); - utils.logInfo('getCurrencyConversion using reciprocal ' + fromCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate); + logInfo('getCurrencyConversion using reciprocal ' + fromCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate); } else { // first defined currency base used as intermediary var anyBaseCurrency = Object.keys(currencyRates.conversions)[0]; @@ -280,11 +302,11 @@ function getCurrencyConversion(fromCurrency, toCurrency = adServerCurrency) { var fromIntermediateConversionRate = currencyRates.conversions[anyBaseCurrency][toCurrency]; conversionRate = roundFloat(toIntermediateConversionRate * fromIntermediateConversionRate, CURRENCY_RATE_PRECISION); - utils.logInfo('getCurrencyConversion using intermediate ' + fromCurrency + ' thru ' + anyBaseCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate); + logInfo('getCurrencyConversion using intermediate ' + fromCurrency + ' thru ' + anyBaseCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate); } } if (!(cacheKey in conversionCache)) { - utils.logMessage('Adding conversionCache value ' + conversionRate + ' for ' + cacheKey); + logMessage('Adding conversionCache value ' + conversionRate + ' for ' + cacheKey); conversionCache[cacheKey] = conversionRate; } return conversionRate; diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js new file mode 100644 index 00000000000..c9caa78e5e7 --- /dev/null +++ b/modules/cwireBidAdapter.js @@ -0,0 +1,275 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { getStorageManager } from '../src/storageManager.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { OUTSTREAM } from '../src/video.js'; +import { + isArray, + isNumber, + generateUUID, + parseSizesInput, + deepAccess, + getParameterByName, + getValue, + getBidIdParameter, + logError, + logWarn, +} from '../src/utils.js'; +import { Renderer } from '../src/Renderer.js'; +import find from 'core-js-pure/features/array/find.js'; + +// ------------------------------------ +const BIDDER_CODE = 'cwire'; +export const ENDPOINT_URL = 'https://embed.cwi.re/delivery/prebid'; +export const RENDERER_URL = 'https://cdn.cwi.re/prebid/renderer/LATEST/renderer.min.js'; +// ------------------------------------ +export const CW_PAGE_VIEW_ID = generateUUID(); +const LS_CWID_KEY = 'cw_cwid'; +const CW_GROUPS_QUERY = 'cwgroups'; +const CW_CREATIVE_QUERY = 'cwcreative'; + +const storage = getStorageManager(); + +/** + * ------------------------------------ + * ------------------------------------ + * @param bid + * @returns {Array} + */ +export function getSlotSizes(bid) { + return parseSizesInput(getAllMediaSizes(bid)); +} + +/** + * ------------------------------------ + * ------------------------------------ + * @param bid + * @returns {*[]} + */ +export function getAllMediaSizes(bid) { + let playerSizes = deepAccess(bid, 'mediaTypes.video.playerSize'); + let videoSizes = deepAccess(bid, 'mediaTypes.video.sizes'); + let bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); + + const sizes = []; + + if (isArray(playerSizes)) { + playerSizes.forEach((s) => { + sizes.push(s); + }) + } + + if (isArray(videoSizes)) { + videoSizes.forEach((s) => { + sizes.push(s); + }) + } + + if (isArray(bannerSizes)) { + bannerSizes.forEach((s) => { + sizes.push(s); + }) + } + return sizes; +} + +const getQueryVariable = (variable) => { + let value = getParameterByName(variable); + if (value === '') { + value = null; + } + return value; +}; + +/** + * ------------------------------------ + * ------------------------------------ + * @param validBidRequests + * @returns {*[]} + */ +export const mapSlotsData = function(validBidRequests) { + const slots = []; + validBidRequests.forEach(bid => { + const bidObj = {}; + // get the pacement and page ids + let placementId = getValue(bid.params, 'placementId'); + let pageId = getValue(bid.params, 'pageId'); + let adUnitElementId = getValue(bid.params, 'adUnitElementId'); + // get the rest of the auction/bid/transaction info + bidObj.auctionId = getBidIdParameter('auctionId', bid); + bidObj.adUnitCode = getBidIdParameter('adUnitCode', bid); + bidObj.adUnitElementId = adUnitElementId; + bidObj.bidId = getBidIdParameter('bidId', bid); + bidObj.bidderRequestId = getBidIdParameter('bidderRequestId', bid); + bidObj.placementId = placementId; + bidObj.pageId = pageId; + bidObj.mediaTypes = getBidIdParameter('mediaTypes', bid); + bidObj.transactionId = getBidIdParameter('transactionId', bid); + bidObj.sizes = getSlotSizes(bid); + slots.push(bidObj); + }); + + return slots; +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + bid.params = bid.params || {}; + + // if ad unit elemt id not provided - use adUnitCode by default + if (!bid.params.adUnitElementId) { + bid.params.adUnitElementId = bid.code; + } + + if (!bid.params.placementId || !isNumber(bid.params.placementId)) { + logError('placementId not provided or invalid'); + return false; + } + + if (!bid.params.pageId || !isNumber(bid.params.pageId)) { + logError('pageId not provided'); + return false; + } + + return true; + }, + + /** + * ------------------------------------ + * Make a server request from the + * list of BidRequests. + * ------------------------------------ + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + let slots = []; + let referer; + try { + referer = getRefererInfo().referer; + slots = mapSlotsData(validBidRequests); + } catch (e) { + logWarn(e); + } + + let refgroups = []; + + const cwCreativeId = getQueryVariable(CW_CREATIVE_QUERY); + const rgQuery = getQueryVariable(CW_GROUPS_QUERY); + if (rgQuery !== null) { + refgroups = rgQuery.split(','); + } + + const localStorageCWID = storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(LS_CWID_KEY) : null; + + const payload = { + cwid: localStorageCWID, + refgroups, + cwcreative: cwCreativeId, + slots: slots, + httpRef: referer || '', + pageViewId: CW_PAGE_VIEW_ID, + }; + + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + + try { + if (typeof bidRequest.data === 'string') { + bidRequest.data = JSON.parse(bidRequest.data); + } + const serverBody = serverResponse.body; + serverBody.bids.forEach((br) => { + const bidReq = find(bidRequest.data.slots, bid => bid.bidId === br.requestId); + + let mediaType = BANNER; + + const bidResponse = { + requestId: br.requestId, + cpm: br.cpm, + bidderCode: BIDDER_CODE, + width: br.dimensions[0], + height: br.dimensions[1], + creativeId: br.creativeId, + currency: br.currency, + netRevenue: br.netRevenue, + ttl: br.ttl, + meta: { + advertiserDomains: br.adomains ? br.advertiserDomains : [], + }, + + }; + + // ------------------------------------ + // IF BANNER + // ------------------------------------ + + if (deepAccess(bidReq, 'mediaTypes.banner')) { + bidResponse.ad = br.html; + } + // ------------------------------------ + // IF VIDEO + // ------------------------------------ + if (deepAccess(bidReq, 'mediaTypes.video')) { + mediaType = VIDEO; + bidResponse.vastXml = br.vastXml; + bidResponse.videoScript = br.html; + const mediaTypeContext = deepAccess(bidReq, 'mediaTypes.video.context'); + if (mediaTypeContext === OUTSTREAM) { + const r = Renderer.install({ + id: bidResponse.requestId, + adUnitCode: bidReq.adUnitCode, + url: RENDERER_URL, + loaded: false, + config: { + ...deepAccess(bidReq, 'mediaTypes.video'), + ...deepAccess(br, 'outstream', {}) + } + }); + + // set renderer + try { + bidResponse.renderer = r; + bidResponse.renderer.setRender(function(bid) { + if (window.CWIRE && window.CWIRE.outstream) { + window.CWIRE.outstream.renderAd(bid); + } + }); + } catch (err) { + logWarn('Prebid Error calling setRender on newRenderer', err); + } + } + } + + bidResponse.mediaType = mediaType; + bidResponses.push(bidResponse); + }); + } catch (e) { + logWarn(e); + } + + return bidResponses; + }, +} +registerBidder(spec); diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md new file mode 100644 index 00000000000..fc0889e05ad --- /dev/null +++ b/modules/cwireBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +Module Name: C-WIRE Bid Adapter +Module Type: Adagio Adapter +Maintainer: dragan@cwire.ch + +## Description + +Connects to C-WIRE demand source to fetch bids. + +## Configuration + + +Below, the list of C-WIRE params and where they can be set. + +| Param name | Global config | AdUnit config | Type | Required | +| ---------- | ------------- | ------------- | ---- | ---------| +| pageId | | x | number | YES | +| placementId | | x | number | YES | +| adUnitElementId | | x | string | NO | + +### adUnit configuration + +```javascript +var adUnits = [ + { + code: 'target_div_id', // REQUIRED + bids: [{ + bidder: 'cwire', + mediaTypes: { + banner: { + sizes: [[1, 1]], + } + }, + params: { + pageId: 1422, // required - number + placementId: 2211521, // required - number + adUnitElementId: 'other_div', // optional, div id to write to, if not set it will default to ad unit code + } + }] + } +]; +``` \ No newline at end of file diff --git a/modules/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js index 1018417300a..cdcc9f1d038 100644 --- a/modules/dailyhuntBidAdapter.js +++ b/modules/dailyhuntBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as mediaTypes from '../src/mediaTypes.js'; -import * as utils from '../src/utils.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'; @@ -80,7 +80,7 @@ const _encodeURIComponent = function (a) { // Extract key from collections. const extractKeyInfo = (collection, key) => { for (let i = 0, result; i < collection.length; i++) { - result = utils.deepAccess(collection[i].params, key); + result = deepAccess(collection[i].params, key); if (result) { return result; } @@ -117,7 +117,7 @@ const createOrtbUserObj = (validBidRequests) => ({ ...extractKeyInfo(validBidReq const createOrtbSiteObj = (validBidRequests, page) => { let site = { ...extractKeyInfo(validBidRequests, `site`), page }; let publisher = createOrtbPublisherObj(validBidRequests); - if (publisher) { + if (!site.publisher) { site.publisher = publisher } return site @@ -125,18 +125,23 @@ const createOrtbSiteObj = (validBidRequests, page) => { const createOrtbPublisherObj = (validBidRequests) => ({ ...extractKeyInfo(validBidRequests, `publisher`) }) +// get bidFloor Function for different creatives +function getBidFloor(bid, creative) { + let floorInfo = typeof (bid.getFloor) == 'function' ? bid.getFloor({ currency: 'USD', mediaType: creative, size: '*' }) : {}; + return Math.floor(floorInfo.floor || (bid.params.bidfloor ? bid.params.bidfloor : 0.0)); +} + const createOrtbImpObj = (bid) => { let params = bid.params let testMode = !!bid.params.test_mode // Validate Banner Request. - let bannerObj = utils.deepAccess(bid.mediaTypes, `banner`); - let nativeObj = utils.deepAccess(bid.mediaTypes, `native`); - let videoObj = utils.deepAccess(bid.mediaTypes, `video`); + let bannerObj = deepAccess(bid.mediaTypes, `banner`); + let nativeObj = deepAccess(bid.mediaTypes, `native`); + let videoObj = deepAccess(bid.mediaTypes, `video`); let imp = { id: bid.bidId, - bidfloor: params.bidfloor ? params.bidfloor : 0, ext: { dailyhunt: { placement_id: params.placement_id, @@ -155,14 +160,17 @@ const createOrtbImpObj = (bid) => { imp.banner = { ...createOrtbImpBannerObj(bid, bannerObj) } + imp.bidfloor = getBidFloor(bid, 'banner'); } else if (nativeObj) { imp.native = { ...createOrtbImpNativeObj(bid, nativeObj) } + imp.bidfloor = getBidFloor(bid, 'native'); } else if (videoObj) { imp.video = { ...createOrtbImpVideoObj(bid, videoObj) } + imp.bidfloor = getBidFloor(bid, 'video'); } return imp; } @@ -178,7 +186,7 @@ const createOrtbImpBannerObj = (bid, bannerObj) => { } const createOrtbImpNativeObj = (bid, nativeObj) => { - const assets = utils._map(bid.nativeParams, (bidParams, key) => { + const assets = _map(bid.nativeParams, (bidParams, key) => { const props = ORTB_NATIVE_PARAMS[key]; const asset = { required: bidParams.required & 1, @@ -215,10 +223,18 @@ const createOrtbImpNativeObj = (bid, nativeObj) => { const createOrtbImpVideoObj = (bid, videoObj) => { let obj = {}; let params = bid.params - if (!utils.isEmpty(bid.params.video)) { + if (!isEmpty(bid.params.video)) { obj = { - ...params.video, - } + topframe: 1, + skip: params.video.skippable || 0, + linearity: params.video.linearity || 1, + minduration: params.video.minduration || 5, + maxduration: params.video.maxduration || 60, + mimes: params.video.mimes || ['video/mp4'], + protocols: getProtocols(params.video), + w: params.video.playerSize[0][0], + h: params.video.playerSize[0][1], + }; } else { obj = { mimes: ['video/mp4'], @@ -230,6 +246,27 @@ const createOrtbImpVideoObj = (bid, videoObj) => { return obj; } +export function getProtocols({protocols}) { + let defaultValue = [2, 3, 5, 6, 7, 8]; + let listProtocols = [ + {key: 'VAST_1_0', value: 1}, + {key: 'VAST_2_0', value: 2}, + {key: 'VAST_3_0', value: 3}, + {key: 'VAST_1_0_WRAPPER', value: 4}, + {key: 'VAST_2_0_WRAPPER', value: 5}, + {key: 'VAST_3_0_WRAPPER', value: 6}, + {key: 'VAST_4_0', value: 7}, + {key: 'VAST_4_0_WRAPPER', value: 8} + ]; + if (protocols) { + return listProtocols.filter(p => { + return protocols.indexOf(p.key) !== -1 + }).map(p => p.value); + } else { + return defaultValue; + } +} + const createServerRequest = (ortbRequest, validBidRequests, isTestMode = 'false') => ({ method: 'POST', url: isTestMode === 'true' ? PROD_PREBID_TEST_ENDPOINT_URL + validBidRequests[0].params.partner_name : PROD_PREBID_ENDPOINT_URL + validBidRequests[0].params.partner_name, @@ -252,7 +289,8 @@ const createPrebidBannerBid = (bid, bidResponse) => ({ currency: 'USD', ad: bidResponse.adm, mediaType: 'banner', - winUrl: bidResponse.nurl + winUrl: bidResponse.nurl, + adomain: bidResponse.adomain }) const createPrebidNativeBid = (bid, bidResponse) => ({ @@ -267,6 +305,7 @@ const createPrebidNativeBid = (bid, bidResponse) => ({ winUrl: bidResponse.nurl, width: bidResponse.w, height: bidResponse.h, + adomain: bidResponse.adomain }) const parseNative = (bid) => { @@ -279,15 +318,15 @@ const parseNative = (bid) => { javascriptTrackers: jstracker ? [ jstracker ] : [] }; assets.forEach(asset => { - if (!utils.isEmpty(asset.title)) { + if (!isEmpty(asset.title)) { result.title = asset.title.text - } else if (!utils.isEmpty(asset.img)) { + } else if (!isEmpty(asset.img)) { result[ORTB_NATIVE_TYPE_MAPPING.img[asset.img.type]] = { url: asset.img.url, height: asset.img.h, width: asset.img.w } - } else if (!utils.isEmpty(asset.data)) { + } else if (!isEmpty(asset.data)) { result[ORTB_NATIVE_TYPE_MAPPING.data[asset.data.type]] = asset.data.value } }); @@ -307,6 +346,7 @@ const createPrebidVideoBid = (bid, bidResponse) => { currency: 'USD', mediaType: 'video', winUrl: bidResponse.nurl, + adomain: bidResponse.adomain }; let videoContext = bid.mediaTypes.video.context; diff --git a/modules/dailyhuntBidAdapter.md b/modules/dailyhuntBidAdapter.md index acfd20a4de0..a08b66fb826 100644 --- a/modules/dailyhuntBidAdapter.md +++ b/modules/dailyhuntBidAdapter.md @@ -99,3 +99,7 @@ Dailyhunt bid adapter supports Banner, Native and Video. } ]; ``` + +## latest commit has all the required support for latest version of prebid above 6.x +## Dailyhunt adapter was there till 4.x and then removed in version 5.x of prebid. +## this doc has been also submitted to https://github.com/prebid/prebid.github.io \ No newline at end of file diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index b00a3eae659..43039e070c3 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -1,330 +1,627 @@ -import * as utils from '../src/utils.js'; +import { getWindowTop, isGptPubadsDefined, deepAccess, getAdUnitSizes, isEmpty } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -const NATIVE_MAP = { - 'body': 2, - 'body2': 10, - 'price': 6, - 'displayUrl': 11, - 'cta': 12 -}; -const NATIVE_IMAGE = [{ - id: 1, - required: 1, +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(); + +const NATIVE_ID_MAP = {}; +const NATIVE_PARAMS = { title: { - len: 140 - } -}, { - id: 2, - required: 1, - img: { type: 3 } -}, { - id: 3, - required: 1, - data: { - type: 11 - } -}, { - id: 4, - required: 0, - data: { + id: 1, + name: 'title' + }, + icon: { + id: 2, + type: 1, + name: 'img' + }, + image: { + id: 3, + type: 3, + name: 'img' + }, + body: { + id: 4, + name: 'data', type: 2 + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + }, + cta: { + id: 6, + type: 12, + name: 'data' + }, + body2: { + id: 7, + name: 'data', + type: 10 + }, + rating: { + id: 8, + name: 'data', + type: 3 + }, + likes: { + id: 9, + name: 'data', + type: 4 + }, + downloads: { + id: 10, + name: 'data', + type: 5 + }, + displayUrl: { + id: 11, + name: 'data', + type: 11 + }, + price: { + id: 12, + name: 'data', + type: 6 + }, + salePrice: { + id: 13, + name: 'data', + type: 7 + }, + address: { + id: 14, + name: 'data', + type: 9 + }, + phone: { + id: 15, + name: 'data', + type: 8 } -}, { - id: 5, - required: 0, - img: { type: 1 } -}, { - id: 6, - required: 0, - data: { - type: 12 - } -}]; +}; -const VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', - 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', - 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', - 'pos', 'companionad', 'api', 'companiontype', 'ext']; +Object.keys(NATIVE_PARAMS).forEach((key) => { + NATIVE_ID_MAP[NATIVE_PARAMS[key].id] = key; +}); +// DEFINE THE PREBID BIDDER SPEC export const spec = { - supportedMediaTypes: [BANNER, NATIVE, VIDEO], + supportedMediaTypes: [BANNER, NATIVE], code: 'datablocks', + + // DATABLOCKS SCOPED OBJECT + db_obj: {metrics_host: 'prebid.dblks.net', metrics: [], metrics_timer: null, metrics_queue_time: 1000, vis_optout: false, source_id: 0}, + + // STORE THE DATABLOCKS BUYERID IN STORAGE + store_dbid: function(dbid) { + let stored = false; + + // CREATE 1 YEAR EXPIRY DATE + let d = new Date(); + d.setTime(Date.now() + (365 * 24 * 60 * 60 * 1000)); + + // TRY TO STORE IN COOKIE + if (storage.cookiesAreEnabled) { + storage.setCookie('_db_dbid', dbid, d.toUTCString(), 'None', null); + stored = true; + } + + // TRY TO STORE IN LOCAL STORAGE + if (storage.localStorageIsEnabled) { + storage.setDataInLocalStorage('_db_dbid', dbid); + stored = true; + } + + return stored; + }, + + // FETCH DATABLOCKS BUYERID FROM STORAGE + get_dbid: function() { + let dbId = ''; + if (storage.cookiesAreEnabled) { + dbId = storage.getCookie('_db_dbid') || ''; + } + + if (!dbId && storage.localStorageIsEnabled) { + dbId = storage.getDataFromLocalStorage('_db_dbid') || ''; + } + return dbId; + }, + + // STORE SYNCS IN STORAGE + store_syncs: function(syncs) { + if (storage.localStorageIsEnabled) { + let syncObj = {}; + syncs.forEach(sync => { + syncObj[sync.id] = sync.uid; + }); + + // FETCH EXISTING SYNCS AND MERGE NEW INTO STORAGE + let storedSyncs = this.get_syncs(); + storage.setDataInLocalStorage('_db_syncs', JSON.stringify(Object.assign(storedSyncs, syncObj))); + + return true; + } + }, + + // GET SYNCS FROM STORAGE + get_syncs: function() { + if (storage.localStorageIsEnabled) { + let syncData = storage.getDataFromLocalStorage('_db_syncs'); + if (syncData) { + return JSON.parse(syncData); + } else { + return {}; + } + } else { + return {}; + } + }, + + // ADD METRIC DATA TO THE METRICS RESPONSE QUEUE + queue_metric: function(metric) { + if (typeof metric === 'object') { + // PUT METRICS IN THE QUEUE + this.db_obj.metrics.push(metric); + + // RESET PREVIOUS TIMER + if (this.db_obj.metrics_timer) { + clearTimeout(this.db_obj.metrics_timer); + } + + // SETUP THE TIMER TO FIRE BACK THE DATA + let scope = this; + this.db_obj.metrics_timer = setTimeout(function() { + scope.send_metrics(); + }, this.db_obj.metrics_queue_time); + + return true; + } else { + return false; + } + }, + + // POST CONSOLIDATED METRICS BACK TO SERVER + send_metrics: function() { + // POST TO SERVER + ajax(`https://${this.db_obj.metrics_host}/a/pb/`, null, JSON.stringify(this.db_obj.metrics), {method: 'POST', withCredentials: true}); + + // RESET THE QUEUE OF METRIC DATA + this.db_obj.metrics = []; + + return true; + }, + + // GET BASIC CLIENT INFORMATION + get_client_info: function () { + let botTest = new BotClientTests(); + let win = getWindowTop(); + return { + 'wiw': win.innerWidth, + 'wih': win.innerHeight, + 'saw': screen ? screen.availWidth : null, + 'sah': screen ? screen.availHeight : null, + 'scd': screen ? screen.colorDepth : null, + 'sw': screen ? screen.width : null, + 'sh': screen ? screen.height : null, + 'whl': win.history.length, + 'wxo': win.pageXOffset, + 'wyo': win.pageYOffset, + 'wpr': win.devicePixelRatio, + 'is_bot': botTest.doTests(), + 'is_hid': win.document.hidden, + 'vs': win.document.visibilityState + }; + }, + + // LISTEN FOR GPT VIEWABILITY EVENTS + get_viewability: function(bid) { + // ONLY RUN ONCE IF PUBLISHER HAS OPTED IN + if (!this.db_obj.vis_optout && !this.db_obj.vis_run) { + this.db_obj.vis_run = true; + + // ADD GPT EVENT LISTENERS + let scope = this; + if (isGptPubadsDefined()) { + if (typeof window['googletag'].pubads().addEventListener == 'function') { + window['googletag'].pubads().addEventListener('impressionViewable', function(event) { + scope.queue_metric({type: 'slot_view', source_id: scope.db_obj.source_id, auction_id: bid.auctionId, div_id: event.slot.getSlotElementId(), slot_id: event.slot.getSlotId().getAdUnitPath()}); + }); + window['googletag'].pubads().addEventListener('slotRenderEnded', function(event) { + scope.queue_metric({type: 'slot_render', source_id: scope.db_obj.source_id, auction_id: bid.auctionId, div_id: event.slot.getSlotElementId(), slot_id: event.slot.getSlotId().getAdUnitPath()}); + }) + } + } + } + }, + + // VALIDATE THE BID REQUEST isBidRequestValid: function(bid) { - return !!(bid.params.host && bid.params.sourceId && - bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.native || bid.mediaTypes.video)); + // SET GLOBAL VARS FROM BIDDER CONFIG + this.db_obj.source_id = bid.params.source_id; + if (bid.params.vis_optout) { + this.db_obj.vis_optout = true; + } + + return !!(bid.params.source_id && bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.native)); }, - buildRequests: function(validBidRequests, bidderRequest) { - if (!validBidRequests.length) { return []; } - let imps = {}; - let site = {}; - let device = {}; - let refurl = utils.parseUrl(bidderRequest.referrer); - let requests = []; + // GENERATE THE RTB REQUEST + buildRequests: function(validRequests, bidderRequest) { + // RETURN EMPTY IF THERE ARE NO VALID REQUESTS + if (!validRequests.length) { + return []; + } - validBidRequests.forEach(bidRequest => { + // CONVERT PREBID NATIVE REQUEST OBJ INTO RTB OBJ + function createNativeRequest(bid) { + const assets = []; + if (bid.nativeParams) { + Object.keys(bid.nativeParams).forEach((key) => { + if (NATIVE_PARAMS[key]) { + const {name, type, id} = NATIVE_PARAMS[key]; + const assetObj = type ? {type} : {}; + let {len, sizes, required, aspect_ratios: aRatios} = bid.nativeParams[key]; + if (len) { + assetObj.len = len; + } + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + let wmin = aRatios.min_width || 0; + let hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + assetObj.wmin = wmin; + assetObj.hmin = hmin; + } + if (sizes && sizes.length) { + sizes = [].concat(...sizes); + assetObj.w = sizes[0]; + assetObj.h = sizes[1]; + } + const asset = {required: required ? 1 : 0, id}; + asset[name] = assetObj; + assets.push(asset); + } + }); + } + return { + ver: '1.2', + request: { + assets: assets, + context: 1, + plcmttype: 1, + ver: '1.2' + } + } + } + let imps = []; + // ITERATE THE VALID REQUESTS AND GENERATE IMP OBJECT + validRequests.forEach(bidRequest => { + // BUILD THE IMP OBJECT let imp = { id: bidRequest.bidId, - tagid: bidRequest.adUnitCode, - secure: window.location.protocol == 'https:' + tagid: bidRequest.params.tagid || bidRequest.adUnitCode, + placement_id: bidRequest.params.placement_id || 0, + secure: window.location.protocol == 'https:', + ortb2: deepAccess(bidRequest, `ortb2Imp`) || {}, + floor: {} + } + + // CHECK FOR FLOORS + if (typeof bidRequest.getFloor === 'function') { + imp.floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); } - if (utils.deepAccess(bidRequest, `mediaTypes.banner`)) { - let sizes = bidRequest.mediaTypes.banner.sizes; - if (sizes.length == 1) { + // BUILD THE SIZES + if (deepAccess(bidRequest, `mediaTypes.banner`)) { + let sizes = getAdUnitSizes(bidRequest); + if (sizes.length) { imp.banner = { w: sizes[0][0], - h: sizes[0][1] - } - } else if (sizes.length > 1) { - imp.banner = { + h: sizes[0][1], format: sizes.map(size => ({ w: size[0], h: size[1] })) }; - } else { - return; - } - } else if (utils.deepAccess(bidRequest, 'mediaTypes.native')) { - let nativeImp = bidRequest.mediaTypes.native; - - if (nativeImp.type) { - let nativeAssets = []; - switch (nativeImp.type) { - case 'image': - nativeAssets = NATIVE_IMAGE; - break; - default: - return; - } - imp.native = JSON.stringify({ assets: nativeAssets }); - } else { - let nativeAssets = []; - let nativeKeys = Object.keys(nativeImp); - nativeKeys.forEach((nativeKey, index) => { - let required = !!nativeImp[nativeKey].required; - let assetId = index + 1; - switch (nativeKey) { - case 'title': - nativeAssets.push({ - id: assetId, - required: required, - title: { - len: nativeImp[nativeKey].len || 140 - } - }); - break; - case 'body': // desc - case 'body2': // desc2 - case 'price': - case 'display_url': - let data = { - id: assetId, - required: required, - data: { - type: NATIVE_MAP[nativeKey] - } - } - if (nativeImp[nativeKey].data && nativeImp[nativeKey].data.len) { data.data.len = nativeImp[nativeKey].data.len; } - - nativeAssets.push(data); - break; - case 'image': - if (nativeImp[nativeKey].sizes && nativeImp[nativeKey].sizes.length) { - nativeAssets.push({ - id: assetId, - required: required, - image: { - type: 3, - w: nativeImp[nativeKey].sizes[0], - h: nativeImp[nativeKey].sizes[1] - } - }) - } - } - }); - imp.native = { - request: JSON.stringify({native: {assets: nativeAssets}}) - }; - } - } else if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { - let video = bidRequest.mediaTypes.video; - let sizes = video.playerSize || bidRequest.sizes || []; - if (sizes.length && Array.isArray(sizes[0])) { - imp.video = { - w: sizes[0][0], - h: sizes[0][1] - }; - } else if (sizes.length == 2 && !Array.isArray(sizes[0])) { - imp.video = { - w: sizes[0], - h: sizes[1] - }; - } else { - return; - } - - if (video.durationRangeSec) { - if (Array.isArray(video.durationRangeSec)) { - if (video.durationRangeSec.length == 1) { - imp.video.maxduration = video.durationRangeSec[0]; - } else if (video.durationRangeSec.length == 2) { - imp.video.minduration = video.durationRangeSec[0]; - imp.video.maxduration = video.durationRangeSec[1]; - } - } else { - imp.video.maxduration = video.durationRangeSec; - } - } - if (bidRequest.params.video) { - Object.keys(bidRequest.params.video).forEach(k => { - if (VIDEO_PARAMS.indexOf(k) > -1) { - imp.video[k] = bidRequest.params.video[k]; - } - }) + // ADD TO THE LIST OF IMP REQUESTS + imps.push(imp); } + } else if (deepAccess(bidRequest, `mediaTypes.native`)) { + // ADD TO THE LIST OF IMP REQUESTS + imp.native = createNativeRequest(bidRequest); + imps.push(imp); } - let host = bidRequest.params.host; - let sourceId = bidRequest.params.sourceId; - imps[host] = imps[host] || {}; - let hostImp = imps[host][sourceId] = imps[host][sourceId] || { imps: [] }; - hostImp.imps.push(imp); - hostImp.subid = hostImp.imps.subid || bidRequest.params.subid || 'blank'; - hostImp.path = 'search'; - hostImp.idParam = 'sid'; - hostImp.protocol = '//'; }); - // Generate Site obj - site.domain = refurl.hostname; - site.page = refurl.protocol + '://' + refurl.hostname + refurl.pathname; + // RETURN EMPTY IF THERE WERE NO PROPER ADUNIT REQUESTS TO BE MADE + if (!imps.length) { + return []; + } + + // GENERATE SITE OBJECT + let site = { + domain: window.location.host, + page: bidderRequest.refererInfo.referer, + schain: validRequests[0].schain || {}, + ext: { + p_domain: config.getConfig('publisherDomain'), + rt: bidderRequest.refererInfo.reachedTop, + frames: bidderRequest.refererInfo.numIframes, + stack: bidderRequest.refererInfo.stack, + timeout: config.getConfig('bidderTimeout') + }, + } + + // ADD REF URL IF FOUND if (self === top && document.referrer) { site.ref = document.referrer; } + + // ADD META KEYWORDS IF FOUND let keywords = document.getElementsByTagName('meta')['keywords']; if (keywords && keywords.content) { site.keywords = keywords.content; } - // Generate Device obj. - device.ip = 'peer'; - device.ua = window.navigator.userAgent; - device.js = 1; - device.language = ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en'; - - RtbRequest(device, site, imps).forEach(formatted => { - requests.push({ - method: 'POST', - url: formatted.url, - data: formatted.body, - options: { - withCredentials: false + // GENERATE DEVICE OBJECT + let device = { + ip: 'peer', + ua: window.navigator.userAgent, + js: 1, + language: ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en', + buyerid: this.get_dbid() || 0, + ext: { + pb_eids: validRequests[0].userIdAsEids || {}, + syncs: this.get_syncs() || {}, + coppa: config.getConfig('coppa') || 0, + gdpr: bidderRequest.gdprConsent || {}, + usp: bidderRequest.uspConsent || {}, + client_info: this.get_client_info(), + ortb2: config.getConfig('ortb2') || {} + } + }; + + let sourceId = validRequests[0].params.source_id || 0; + let host = validRequests[0].params.host || 'prebid.dblks.net'; + + // RETURN WITH THE REQUEST AND PAYLOAD + return { + method: 'POST', + url: `https://${host}/openrtb/?sid=${sourceId}`, + data: { + id: bidderRequest.auctionId, + imp: imps, + site: site, + device: device + }, + options: { + withCredentials: true + } + }; + }, + + // INITIATE USER SYNCING + getUserSyncs: function(options, rtbResponse, gdprConsent) { + const syncs = []; + let bidResponse = rtbResponse[0].body; + let scope = this; + + // LISTEN FOR SYNC DATA FROM IFRAME TYPE SYNC + window.addEventListener('message', function (event) { + if (event.data.sentinel && event.data.sentinel === 'dblks_syncData') { + // STORE FOUND SYNCS + if (event.data.syncs) { + scope.store_syncs(event.data.syncs); } - }) + } }); - return requests; - - function RtbRequest(device, site, imps) { - let collection = []; - Object.keys(imps).forEach(host => { - let sourceIds = imps[host]; - Object.keys(sourceIds).forEach(sourceId => { - let impObj = sourceIds[sourceId]; - collection.push({ - url: `https://${host}/${impObj.path}/?${impObj.idParam}=${sourceId}`, - body: { - id: bidderRequest.auctionId, - imp: impObj.imps, - site: Object.assign({ id: impObj.subid || 'blank' }, site), - device: Object.assign({}, device) - } - }) - }) + + // POPULATE GDPR INFORMATION + let gdprData = { + gdpr: 0, + gdprConsent: '' + } + if (typeof gdprConsent === 'object') { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprData.gdpr = Number(gdprConsent.gdprApplies); + gdprData.gdprConsent = gdprConsent.consentString; + } else { + gdprData.gdprConsent = gdprConsent.consentString; + } + } + + // EXTRACT BUYERID COOKIE VALUE FROM BID RESPONSE AND PUT INTO STORAGE + let dbBuyerId = this.get_dbid() || ''; + if (bidResponse.ext && bidResponse.ext.buyerid) { + dbBuyerId = bidResponse.ext.buyerid; + this.store_dbid(dbBuyerId); + } + + // EXTRACT USERSYNCS FROM BID RESPONSE + if (bidResponse.ext && bidResponse.ext.syncs) { + bidResponse.ext.syncs.forEach(sync => { + if (checkValid(sync)) { + syncs.push(addParams(sync)); + } }) + } + + // APPEND PARAMS TO SYNC URL + function addParams(sync) { + // PARSE THE URL + try { + let url = new URL(sync.url); + let urlParams = {}; + for (const [key, value] of url.searchParams.entries()) { + urlParams[key] = value; + }; + + // APPLY EXTRA VARS + urlParams.gdpr = gdprData.gdpr; + urlParams.gdprConsent = gdprData.gdprConsent; + urlParams.bidid = bidResponse.bidid; + urlParams.id = bidResponse.id; + urlParams.uid = dbBuyerId; - return collection; + // REBUILD URL + sync.url = `${url.origin}${url.pathname}?${Object.keys(urlParams).map(key => key + '=' + encodeURIComponent(urlParams[key])).join('&')}`; + } catch (e) {}; + + // RETURN THE REBUILT URL + return sync; + } + + // ENSURE THAT THE SYNC TYPE IS VALID AND HAS PERMISSION + function checkValid(sync) { + if (!sync.type || !sync.url) { + return false; + } + switch (sync.type) { + case 'iframe': + return options.iframeEnabled; + case 'image': + return options.pixelEnabled; + default: + return false; + } } + return syncs; }, - interpretResponse: function(serverResponse, bidRequest) { - if (!serverResponse || !serverResponse.body || !serverResponse.body.seatbid) { - return []; + + // DATABLOCKS WON THE AUCTION - REPORT SUCCESS + onBidWon: function(bid) { + this.queue_metric({type: 'bid_won', source_id: bid.params[0].source_id, req_id: bid.requestId, slot_id: bid.adUnitCode, auction_id: bid.auctionId, size: bid.size, cpm: bid.cpm, pb: bid.adserverTargeting.hb_pb, rt: bid.timeToRespond, ttl: bid.ttl}); + }, + + // TARGETING HAS BEEN SET + onSetTargeting: function(bid) { + // LISTEN FOR VIEWABILITY EVENTS + this.get_viewability(bid); + }, + + // PARSE THE RTB RESPONSE AND RETURN FINAL RESULTS + interpretResponse: function(rtbResponse, bidRequest) { + // CONVERT NATIVE RTB RESPONSE INTO PREBID RESPONSE + function parseNative(native) { + const {assets, link, imptrackers, jstracker} = native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || [], + impressionTrackers: imptrackers || [], + javascriptTrackers: jstracker ? [jstracker] : [] + }; + + (assets || []).forEach((asset) => { + const {id, img, data, title} = asset; + const key = NATIVE_ID_MAP[id]; + if (key) { + if (!isEmpty(title)) { + result.title = title.text + } else if (!isEmpty(img)) { + result[key] = { + url: img.url, + height: img.h, + width: img.w + } + } else if (!isEmpty(data)) { + result[key] = data.value; + } + } + }); + + return result; } - let body = serverResponse.body; - - let bids = body.seatbid - .map(seatbid => seatbid.bid) - .reduce((memo, bid) => memo.concat(bid), []); - let req = bidRequest.data; - let reqImps = req.imp; - - return bids.map(rtbBid => { - let imp; - for (let i in reqImps) { - let testImp = reqImps[i] - if (testImp.id == rtbBid.impid) { - imp = testImp; + + let bids = []; + let resBids = deepAccess(rtbResponse, 'body.seatbid') || []; + resBids.forEach(bid => { + let resultItem = {requestId: bid.id, cpm: bid.price, creativeId: bid.crid, currency: bid.currency || 'USD', netRevenue: true, ttl: bid.ttl || 360, meta: {advertiserDomains: bid.adomain}}; + + let mediaType = deepAccess(bid, 'ext.mtype') || ''; + switch (mediaType) { + case 'banner': + bids.push(Object.assign({}, resultItem, {mediaType: BANNER, width: bid.w, height: bid.h, ad: bid.adm})); + break; + + case 'native': + let nativeResult = JSON.parse(bid.adm); + bids.push(Object.assign({}, resultItem, {mediaType: NATIVE, native: parseNative(nativeResult.native)})); + break; + + default: break; - } } - let br = { - requestId: rtbBid.impid, - cpm: rtbBid.price, - creativeId: rtbBid.crid, - currency: rtbBid.currency || 'USD', - netRevenue: true, - ttl: 360 - }; - if (!imp) { - return br; - } else if (imp.banner) { - br.mediaType = BANNER; - br.width = rtbBid.w; - br.height = rtbBid.h; - br.ad = rtbBid.adm; - } else if (imp.native) { - br.mediaType = NATIVE; - - let reverseNativeMap = {}; - let nativeKeys = Object.keys(NATIVE_MAP); - nativeKeys.forEach(k => { - reverseNativeMap[NATIVE_MAP[k]] = k; - }); + }) + + return bids; + } +}; + +// DETECT BOTS +export class BotClientTests { + constructor() { + this.tests = { + headless_chrome: function() { + if (self.navigator) { + if (self.navigator.webdriver) { + return true; + } + } + + return false; + }, - let idMap = {}; - let nativeReq = JSON.parse(imp.native.request); - if (nativeReq.native && nativeReq.native.assets) { - nativeReq.native.assets.forEach(asset => { - if (asset.data) { idMap[asset.id] = reverseNativeMap[asset.data.type]; } + selenium: function () { + let response = false; + + if (window && document) { + let results = [ + 'webdriver' in window, + '_Selenium_IDE_Recorder' in window, + 'callSelenium' in window, + '_selenium' in window, + '__webdriver_script_fn' in document, + '__driver_evaluate' in document, + '__webdriver_evaluate' in document, + '__selenium_evaluate' in document, + '__fxdriver_evaluate' in document, + '__driver_unwrapped' in document, + '__webdriver_unwrapped' in document, + '__selenium_unwrapped' in document, + '__fxdriver_unwrapped' in document, + '__webdriver_script_func' in document, + document.documentElement.getAttribute('selenium') !== null, + document.documentElement.getAttribute('webdriver') !== null, + document.documentElement.getAttribute('driver') !== null + ]; + + results.forEach(result => { + if (result === true) { + response = true; + } }) } - const nativeResponse = JSON.parse(rtbBid.adm); - const { assets, link, imptrackers, jstrackers } = nativeResponse.native; - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || undefined, - impressionTrackers: imptrackers || undefined, - javascriptTrackers: jstrackers ? [jstrackers] : undefined - }; - assets.forEach(asset => { - if (asset.title) { - result.title = asset.title.text; - } else if (asset.img) { - result.image = asset.img.url; - } else if (idMap[asset.id]) { - result[idMap[asset.id]] = asset.data.value; - } - }) - br.native = result; - } else if (imp.video) { - br.mediaType = VIDEO; - br.width = rtbBid.w; - br.height = rtbBid.h; - if (rtbBid.adm) { br.vastXml = rtbBid.adm; } else if (rtbBid.nurl) { br.vastUrl = rtbBid.nurl; } + return response; + }, + } + } + doTests() { + let response = false; + for (const i of Object.keys(this.tests)) { + if (this.tests[i]() === true) { + response = true; } - return br; - }); + } + return response; } +} -}; +// INIT OUR BIDDER WITH PREBID registerBidder(spec); diff --git a/modules/datablocksBidAdapter.md b/modules/datablocksBidAdapter.md index e30cd361974..ad2eb4afc53 100644 --- a/modules/datablocksBidAdapter.md +++ b/modules/datablocksBidAdapter.md @@ -8,8 +8,8 @@ Maintainer: support@datablocks.net # Description -Connects to Datablocks Version 5 Platform -Banner Native and Video +Connects to Datablocks Exchange +Banner and Native # Test Parameters @@ -27,12 +27,13 @@ Banner Native and Video { bidder: 'datablocks', params: { - sourceId: 12345, + source_id: 12345, host: 'prebid.datablocks.net' } } ] - }, { + }, + { code: 'native-div', mediaTypes : { native: { @@ -44,30 +45,11 @@ Banner Native and Video { bidder: 'datablocks', params: { - sourceId: 12345, + source_id: 12345, host: 'prebid.datablocks.net' } - }, { - code: 'video-div', - mediaTypes : { - video: { - playerSize:[500,400], - durationRangeSec:[15,30], - context: "linear" - } - }, - bids: [ - { - bidder: 'datablocks', - params: { - sourceId: 12345, - host: 'prebid.datablocks.net', - video: { - mimes:["video/flv"] - } - } } ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/dchain.js b/modules/dchain.js new file mode 100644 index 00000000000..6a1bd1ebf70 --- /dev/null +++ b/modules/dchain.js @@ -0,0 +1,149 @@ +import includes from 'core-js-pure/features/array/includes.js'; +import { config } from '../src/config.js'; +import { getHook } from '../src/hook.js'; +import { _each, isStr, isArray, isPlainObject, hasOwn, deepClone, deepAccess, logWarn, logError } from '../src/utils.js'; + +const shouldBeAString = ' should be a string'; +const shouldBeAnObject = ' should be an object'; +const shouldBeAnArray = ' should be an Array'; +const shouldBeValid = ' is not a valid dchain property'; +const MODE = { + STRICT: 'strict', + RELAXED: 'relaxed', + OFF: 'off' +}; +const MODES = []; // an array of modes +_each(MODE, mode => MODES.push(mode)); + +export function checkDchainSyntax(bid, mode) { + let dchainObj = deepClone(bid.meta.dchain); + let failPrefix = 'Detected something wrong in bid.meta.dchain object for bid:'; + let failMsg = ''; + const dchainPropList = ['ver', 'complete', 'nodes', 'ext']; + + function appendFailMsg(msg) { + failMsg += '\n' + msg; + } + + function printFailMsg() { + if (mode === MODE.STRICT) { + logError(failPrefix, bid, '\n', dchainObj, failMsg); + } else { + logWarn(failPrefix, bid, `\n`, dchainObj, failMsg); + } + } + + let dchainProps = Object.keys(dchainObj); + dchainProps.forEach(prop => { + if (!includes(dchainPropList, prop)) { + appendFailMsg(`dchain.${prop}` + shouldBeValid); + } + }); + + if (dchainObj.complete !== 0 && dchainObj.complete !== 1) { + appendFailMsg(`dchain.complete should be 0 or 1`); + } + + if (!isStr(dchainObj.ver)) { + appendFailMsg(`dchain.ver` + shouldBeAString); + } + + if (hasOwn(dchainObj, 'ext')) { + if (!isPlainObject(dchainObj.ext)) { + appendFailMsg(`dchain.ext` + shouldBeAnObject); + } + } + + if (!isArray(dchainObj.nodes)) { + appendFailMsg(`dchain.nodes` + shouldBeAnArray); + printFailMsg(); + if (mode === MODE.STRICT) return false; + } else { + const nodesPropList = ['asi', 'bsid', 'rid', 'name', 'domain', 'ext']; + dchainObj.nodes.forEach((node, index) => { + if (!isPlainObject(node)) { + appendFailMsg(`dchain.nodes[${index}]` + shouldBeAnObject); + } else { + let nodeProps = Object.keys(node); + nodeProps.forEach(prop => { + if (!includes(nodesPropList, prop)) { + appendFailMsg(`dchain.nodes[${index}].${prop}` + shouldBeValid); + } + + if (prop === 'ext') { + if (!isPlainObject(node.ext)) { + appendFailMsg(`dchain.nodes[${index}].ext` + shouldBeAnObject); + } + } else { + if (!isStr(node[prop])) { + appendFailMsg(`dchain.nodes[${index}].${prop}` + shouldBeAString); + } + } + }); + } + }); + } + + if (failMsg.length > 0) { + printFailMsg(); + if (mode === MODE.STRICT) { + return false; + } + } + return true; +} + +function isValidDchain(bid) { + let mode = MODE.STRICT; + const dchainConfig = config.getConfig('dchain'); + + if (dchainConfig && isStr(dchainConfig.validation) && MODES.indexOf(dchainConfig.validation) != -1) { + mode = dchainConfig.validation; + } + + if (mode === MODE.OFF) { + return true; + } else { + return checkDchainSyntax(bid, mode); + } +} + +export function addBidResponseHook(fn, adUnitCode, bid) { + const basicDchain = { + ver: '1.0', + complete: 0, + nodes: [] + }; + + if (deepAccess(bid, 'meta.networkId') && deepAccess(bid, 'meta.networkName')) { + basicDchain.nodes.push({ name: bid.meta.networkName, bsid: bid.meta.networkId.toString() }); + } + basicDchain.nodes.push({ name: bid.bidderCode }); + + let bidDchain = deepAccess(bid, 'meta.dchain'); + if (bidDchain && isPlainObject(bidDchain)) { + let result = isValidDchain(bid); + + if (result) { + // extra check in-case mode is OFF and there is a setup issue + if (isArray(bidDchain.nodes)) { + bid.meta.dchain.nodes.push({ asi: bid.bidderCode }); + } else { + logWarn('bid.meta.dchain.nodes did not exist or was not an array; did not append prebid dchain.', bid); + } + } else { + // remove invalid dchain + delete bid.meta.dchain; + } + } else { + bid.meta.dchain = basicDchain; + } + + fn(adUnitCode, bid); +} + +export function init() { + getHook('addBidResponse').before(addBidResponseHook, 35); +} + +init(); diff --git a/modules/dchain.md b/modules/dchain.md new file mode 100644 index 00000000000..f01b3483f3c --- /dev/null +++ b/modules/dchain.md @@ -0,0 +1,45 @@ +# dchain module + +Refer: +- https://iabtechlab.com/buyers-json-demand-chain/ + +## Sample code for dchain setConfig and dchain object +``` +pbjs.setConfig({ + "dchain": { + "validation": "strict" + } +}); +``` + +``` +bid.meta.dchain: { + "complete": 0, + "ver": "1.0", + "ext": {...}, + "nodes": [{ + "asi": "abc", + "bsid": "123", + "rid": "d4e5f6", + "name": "xyz", + "domain": "mno", + "ext": {...} + }, ...] +} +``` + +## Workflow +The dchain module is not enabled by default as it may not be necessary for all publishers. +If required, dchain module can be included as following +``` + $ gulp build --modules=dchain,pubmaticBidAdapter,openxBidAdapter,rubiconBidAdapter,sovrnBidAdapter +``` + +The dchain module will validate a bidder's dchain object (if it was defined). Bidders should assign their dchain object into `bid.meta` field. If the dchain object is valid, it will remain in the bid object for later use. + +If it was not defined, the dchain will create a default dchain object for prebid. + +## Validation modes +- ```strict```: It is the default validation mode. In this mode, dchain object will not be accpeted from adapters if it is invalid. Errors are thrown for invalid dchain object. +- ```relaxed```: In this mode, errors are thrown for an invalid dchain object but the invalid dchain object is still accepted from adapters. +- ```off```: In this mode, no validations are performed and dchain object is accepted as is from adapters. \ No newline at end of file diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js new file mode 100644 index 00000000000..2a179641424 --- /dev/null +++ b/modules/debugging/bidInterceptor.js @@ -0,0 +1,229 @@ +import { + deepAccess, + deepClone, + deepEqual, + delayExecution, + prefixLog, + mergeDeep +} from '../../src/utils.js'; +const { logMessage, logWarn, logError } = prefixLog('DEBUG:'); + +/** + * @typedef {Number|String|boolean|null|undefined} Scalar + */ + +export function BidInterceptor(opts = {}) { + ({setTimeout: this.setTimeout = window.setTimeout.bind(window)} = opts); + this.rules = []; +} + +Object.assign(BidInterceptor.prototype, { + DEFAULT_RULE_OPTIONS: { + delay: 0 + }, + serializeConfig(ruleDefs) { + function isSerializable(ruleDef, i) { + const serializable = deepEqual(ruleDef, JSON.parse(JSON.stringify(ruleDef)), {checkTypes: true}); + if (!serializable && !deepAccess(ruleDef, 'options.suppressWarnings')) { + logWarn(`Bid interceptor rule definition #${i + 1} is not serializable and will be lost after a refresh. Rule definition: `, ruleDef); + } + return serializable; + } + return ruleDefs.filter(isSerializable); + }, + updateConfig(config) { + this.rules = (config.intercept || []).map((ruleDef, i) => this.rule(ruleDef, i + 1)) + }, + /** + * @typedef {Object} RuleOptions + * @property {Number} [delay=0] delay between bid interception and mocking of response (to simulate network delay) + * @property {boolean} [suppressWarnings=false] if enabled, do not warn about unserializable rules + * + * @typedef {Object} Rule + * @property {Number} no rule number (used only as an identifier for logging) + * @property {function({}, {}): boolean} match a predicate function that tests a bid against this rule + * @property {ReplacerFn} replacer generator function for mock bid responses + * @property {RuleOptions} options + */ + + /** + * @param {{}} ruleDef + * @param {Number} ruleNo + * @returns {Rule} + */ + rule(ruleDef, ruleNo) { + return { + no: ruleNo, + match: this.matcher(ruleDef.when, ruleNo), + replace: this.replacer(ruleDef.then || {}, ruleNo), + options: Object.assign({}, this.DEFAULT_RULE_OPTIONS, ruleDef.options), + } + }, + /** + * @typedef {Function} MatchPredicate + * @param {*} candidate a bid to match, or a portion of it if used inside an ObjectMather. + * e.g. matcher((bid, bidRequest) => ....) or matcher({property: (property, bidRequest) => ...}) + * @param {BidRequest} bidRequest the request `candidate` belongs to + * @returns {boolean} + * + * @typedef {{[key]: Scalar|RegExp|MatchPredicate|ObjectMatcher}} ObjectMatcher + */ + + /** + * @param {MatchPredicate|ObjectMatcher} matchDef matcher definition + * @param {Number} ruleNo + * @returns {MatchPredicate} a predicate function that matches a bid against the given `matchDef` + */ + matcher(matchDef, ruleNo) { + if (typeof matchDef === 'function') { + return matchDef; + } + if (typeof matchDef !== 'object') { + logError(`Invalid 'when' definition for debug bid interceptor (in rule #${ruleNo})`); + return () => false; + } + function matches(candidate, {ref = matchDef, args = []}) { + return Object.entries(ref).map(([key, val]) => { + const cVal = candidate[key]; + if (val instanceof RegExp) { + return val.exec(cVal) != null; + } + if (typeof val === 'function') { + return !!val(cVal, ...args); + } + if (typeof val === 'object') { + return matches(cVal, {ref: val, args}); + } + return cVal === val; + }).every((i) => i); + } + return (candidate, ...args) => matches(candidate, {args}); + }, + /** + * @typedef {Function} ReplacerFn + * @param {*} bid a bid that was intercepted + * @param {BidRequest} bidRequest the request `bid` belongs to + * @returns {*} the response to mock for `bid`, or a portion of it if used inside an ObjectReplacer. + * e.g. replacer((bid, bidRequest) => mockResponse) or replacer({property: (bid, bidRequest) => mockProperty}) + * + * @typedef {{[key]: ReplacerFn|ObjectReplacer|*}} ObjectReplacer + */ + + /** + * @param {ReplacerFn|ObjectReplacer} replDef replacer definition + * @param ruleNo + * @return {ReplacerFn} + */ + replacer(replDef, ruleNo) { + let replFn; + if (typeof replDef === 'function') { + replFn = ({args}) => replDef(...args); + } else if (typeof replDef !== 'object') { + logError(`Invalid 'then' definition for debug bid interceptor (in rule #${ruleNo})`); + replFn = () => ({}); + } else { + replFn = ({args, ref = replDef}) => { + const result = {}; + Object.entries(ref).forEach(([key, val]) => { + if (typeof val === 'function') { + result[key] = val(...args); + } else if (typeof val === 'object') { + result[key] = replFn({args, ref: val}) + } else { + result[key] = val; + } + }); + return result; + } + } + return (bid, ...args) => { + const response = this.responseDefaults(bid); + mergeDeep(response, replFn({args: [bid, ...args]})); + if (!response.ad) { + response.ad = this.defaultAd(bid, response); + } + response.isDebug = true; + return response; + } + }, + responseDefaults(bid) { + return { + requestId: bid.bidId, + cpm: 3.5764, + currency: 'EUR', + width: 300, + height: 250, + ttl: 360, + creativeId: 'mock-creative-id', + netRevenue: false, + meta: {} + }; + }, + defaultAd(bid, bidResponse) { + return ``; + }, + /** + * Match a candidate bid against all registered rules. + * + * @param {{}} candidate + * @param args + * @returns {Rule|undefined} the first matching rule, or undefined if no match was found. + */ + match(candidate, ...args) { + return this.rules.find((rule) => rule.match(candidate, ...args)); + }, + /** + * Match a set of bids against all registered rules. + * + * @param bids + * @param bidRequest + * @returns {[{bid: *, rule: Rule}[], *[]]} a 2-tuple for matching bids (decorated with the matching rule) and + * non-matching bids. + */ + matchAll(bids, bidRequest) { + const [matches, remainder] = [[], []]; + bids.forEach((bid) => { + const rule = this.match(bid, bidRequest); + if (rule != null) { + matches.push({rule: rule, bid: bid}); + } else { + remainder.push(bid); + } + }) + return [matches, remainder]; + }, + /** + * Run a set of bids against all registered rules, filter out those that match, + * and generate mock responses for them. + * + * @param {{}[]} bids? + * @param {BidRequest} bidRequest + * @param {function(*)} addBid called once for each mock response + * @param {function()} done called once after all mock responses have been run through `addBid` + * @returns {{bids: {}[], bidRequest: {}} remaining bids that did not match any rule (this applies also to + * bidRequest.bids) + */ + intercept({bids, bidRequest, addBid, done}) { + if (bids == null) { + bids = bidRequest.bids; + } + const [matches, remainder] = this.matchAll(bids, bidRequest); + if (matches.length > 0) { + const callDone = delayExecution(done, matches.length); + matches.forEach((match) => { + const mockResponse = match.rule.replace(match.bid, bidRequest); + const delay = match.rule.options.delay; + logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response:`, match.bid, mockResponse) + this.setTimeout(() => { + addBid(mockResponse, match.bid); + callDone(); + }, delay) + }); + bidRequest = deepClone(bidRequest); + bids = bidRequest.bids = remainder; + } else { + this.setTimeout(done, 0); + } + return {bids, bidRequest}; + } +}); diff --git a/modules/debugging/index.js b/modules/debugging/index.js new file mode 100644 index 00000000000..72692c3fc98 --- /dev/null +++ b/modules/debugging/index.js @@ -0,0 +1,62 @@ +import {deepClone, delayExecution} from '../../src/utils.js'; +import {processBidderRequests} from '../../src/adapters/bidderFactory.js'; +import {BidInterceptor} from './bidInterceptor.js'; +import {hook} from '../../src/hook.js'; +import {pbsBidInterceptor} from './pbsInterceptor.js'; +import { + onDisableOverrides, + onEnableOverrides, + saveDebuggingConfig +} from '../../src/debugging.js'; + +const interceptorHooks = []; +const bidInterceptor = new BidInterceptor(); + +saveDebuggingConfig.before(function (next, debugConfig, ...args) { + if (debugConfig.intercept) { + debugConfig = deepClone(debugConfig); + debugConfig.intercept = bidInterceptor.serializeConfig(debugConfig.intercept); + } + next(debugConfig, ...args); +}); + +function resetHooks(enable) { + interceptorHooks.forEach(([getHookFn, interceptor]) => { + getHookFn().getHooks({hook: interceptor}).remove(); + }); + if (enable) { + interceptorHooks.forEach(([getHookFn, interceptor]) => { + getHookFn().before(interceptor); + }) + } +} + +onEnableOverrides.push((overrides) => { + bidInterceptor.updateConfig(overrides); + resetHooks(true); +}); + +onDisableOverrides.push(() => { + bidInterceptor.updateConfig({}); + resetHooks(false); +}) + +function registerBidInterceptor(getHookFn, interceptor) { + const interceptBids = (...args) => bidInterceptor.intercept(...args); + interceptorHooks.push([getHookFn, function (next, ...args) { + interceptor(next, interceptBids, ...args) + }]); +} + +export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, cbs) { + const done = delayExecution(cbs.onCompletion, 2); + ({bids, bidRequest} = interceptBids({bids, bidRequest, addBid: cbs.onBid, done})); + if (bids.length === 0) { + done(); + } else { + next(spec, bids, bidRequest, ajax, wrapCallback, {...cbs, onCompletion: done}); + } +} + +registerBidInterceptor(() => processBidderRequests, bidderBidInterceptor); +registerBidInterceptor(() => hook.get('processPBSRequest'), pbsBidInterceptor); diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js new file mode 100644 index 00000000000..5af2384cad9 --- /dev/null +++ b/modules/debugging/pbsInterceptor.js @@ -0,0 +1,38 @@ +import {deepClone, delayExecution} from '../../src/utils.js'; +import {createBid} from '../../src/bidfactory.js'; +import * as CONSTANTS from '../../src/constants.json'; + +export function pbsBidInterceptor (next, interceptBids, s2sBidRequest, bidRequests, ajax, { + onResponse, + onError, + onBid +}) { + let responseArgs; + const done = delayExecution(() => onResponse(...responseArgs), bidRequests.length + 1) + function signalResponse(...args) { + responseArgs = args; + done(); + } + function addBid(bid, bidRequest) { + onBid({ + adUnit: bidRequest.adUnitCode, + bid: Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid) + }) + } + bidRequests = bidRequests + .map((req) => interceptBids({bidRequest: req, addBid, done}).bidRequest) + .filter((req) => req.bids.length > 0) + + if (bidRequests.length > 0) { + const bidIds = new Set(); + bidRequests.forEach((req) => req.bids.forEach((bid) => bidIds.add(bid.bidId))); + s2sBidRequest = deepClone(s2sBidRequest); + s2sBidRequest.ad_units.forEach((unit) => { + unit.bids = unit.bids.filter((bid) => bidIds.has(bid.bid_id)); + }) + s2sBidRequest.ad_units = s2sBidRequest.ad_units.filter((unit) => unit.bids.length > 0); + next(s2sBidRequest, bidRequests, ajax, {onResponse: signalResponse, onError, onBid}); + } else { + signalResponse(true, []); + } +} diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index a6a6cac6570..94167b92bb0 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -1,13 +1,38 @@ +import { generateUUID, deepSetValue, deepAccess, isArray, isInteger, logError, logWarn } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'deepintent'; const BIDDER_ENDPOINT = 'https://prebid.deepintent.com/prebid'; const USER_SYNC_URL = 'https://cdn.deepintent.com/syncpixel.html'; const DI_M_V = '1.0.0'; +export const ORTB_VIDEO_PARAMS = { + 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), + 'minduration': (value) => isInteger(value), + 'maxduration': (value) => isInteger(value), + 'protocols': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 10), + 'w': (value) => isInteger(value), + 'h': (value) => isInteger(value), + 'startdelay': (value) => isInteger(value), + 'placement': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 5), + 'linearity': (value) => [1, 2].indexOf(value) !== -1, + 'skip': (value) => [0, 1].indexOf(value) !== -1, + 'skipmin': (value) => isInteger(value), + 'skipafter': (value) => isInteger(value), + 'sequence': (value) => isInteger(value), + 'battr': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 17), + 'maxextended': (value) => isInteger(value), + 'minbitrate': (value) => isInteger(value), + 'maxbitrate': (value) => isInteger(value), + 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, + 'playbackmethod': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6), + 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, + 'delivery': (value) => [1, 2, 3].indexOf(value) !== -1, + 'pos': (value) => [0, 1, 2, 3, 4, 5, 6, 7].indexOf(value) !== -1, + 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6) +}; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], aliases: [], // tagId is mandatory param @@ -15,16 +40,38 @@ export const spec = { let valid = false; if (bid && bid.params && bid.params.tagId) { if (typeof bid.params.tagId === 'string' || bid.params.tagId instanceof String) { - valid = true; + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + if (bid.mediaTypes[VIDEO].hasOwnProperty('context')) { + valid = true; + } + } else { + valid = true; + } } } return valid; }, - interpretResponse: function(bidResponse, request) { + interpretResponse: function(bidResponse, bidRequest) { let responses = []; if (bidResponse && bidResponse.body) { - let bids = bidResponse.body.seatbid && bidResponse.body.seatbid[0] ? bidResponse.body.seatbid[0].bid : []; - responses = bids.map(bid => formatResponse(bid)) + try { + let bids = bidResponse.body.seatbid && bidResponse.body.seatbid[0] ? bidResponse.body.seatbid[0].bid : []; + if (bids) { + bids.forEach(bidObj => { + let newBid = formatResponse(bidObj); + let mediaType = _checkMediaType(bidObj); + if (mediaType === BANNER) { + newBid.mediaType = BANNER; + } else if (mediaType === VIDEO) { + newBid.mediaType = VIDEO; + newBid.vastXml = bidObj.adm; + } + responses.push(newBid); + }); + } + } catch (err) { + logError(err); + } } return responses; }, @@ -32,7 +79,7 @@ export const spec = { var user = validBidRequests.map(bid => buildUser(bid)); clean(user); const openRtbBidRequest = { - id: utils.generateUUID(), + id: generateUUID(), at: 1, imp: validBidRequests.map(bid => buildImpression(bid)), site: buildSite(bidderRequest), @@ -41,12 +88,12 @@ export const spec = { }; if (bidderRequest && bidderRequest.uspConsent) { - utils.deepSetValue(openRtbBidRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(openRtbBidRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); } if (bidderRequest && bidderRequest.gdprConsent) { - utils.deepSetValue(openRtbBidRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - utils.deepSetValue(openRtbBidRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + deepSetValue(openRtbBidRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(openRtbBidRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } injectEids(openRtbBidRequest, validBidRequests); @@ -73,6 +120,17 @@ export const spec = { } }; +function _checkMediaType(bid) { + let videoRegex = new RegExp(/VAST\s+version/); + let mediaType; + if (bid.adm && bid.adm.indexOf('deepintent_wrapper') >= 0) { + mediaType = BANNER; + } else if (videoRegex.test(bid.adm)) { + mediaType = VIDEO; + } + return mediaType; +} + function clean(obj) { for (let propName in obj) { if (obj[propName] === null || obj[propName] === undefined) { @@ -100,16 +158,55 @@ function formatResponse(bid) { } function buildImpression(bid) { - return { + let impression = {}; + impression = { id: bid.bidId, tagid: bid.params.tagId || '', - secure: window.location.protocol === 'https' ? 1 : 0, - banner: buildBanner(bid), + secure: window.location.protocol === 'https:' ? 1 : 0, displaymanager: 'di_prebid', displaymanagerver: DI_M_V, ext: buildCustomParams(bid) }; + if (deepAccess(bid, 'mediaTypes.banner')) { + impression['banner'] = buildBanner(bid); + } + if (deepAccess(bid, 'mediaTypes.video')) { + impression['video'] = _buildVideo(bid); + } + return impression; } + +function _buildVideo(bid) { + const videoObj = {}; + const videoAdUnitParams = deepAccess(bid, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bid, 'params.video', {}); + const computedParams = {}; + + if (Array.isArray(videoAdUnitParams.playerSize)) { + const tempSize = (Array.isArray(videoAdUnitParams.playerSize[0])) ? videoAdUnitParams.playerSize[0] : videoAdUnitParams.playerSize; + computedParams.w = tempSize[0]; + computedParams.h = tempSize[1]; + } + + const videoParams = { + ...computedParams, + ...videoAdUnitParams, + ...videoBidderParams + }; + + Object.keys(ORTB_VIDEO_PARAMS).forEach(paramName => { + if (videoParams.hasOwnProperty(paramName)) { + if (ORTB_VIDEO_PARAMS[paramName](videoParams[paramName])) { + videoObj[paramName] = videoParams[paramName]; + } else { + logWarn(`The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`); + } + } + }); + + return videoObj; +}; + function buildCustomParams(bid) { if (bid.params && bid.params.custom) { return { @@ -134,18 +231,19 @@ function buildUser(bid) { } function injectEids(openRtbBidRequest, validBidRequests) { - const bidUserIdAsEids = utils.deepAccess(validBidRequests, '0.userIdAsEids'); - if (utils.isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { - utils.deepSetValue(openRtbBidRequest, 'user.eids', bidUserIdAsEids); + const bidUserIdAsEids = deepAccess(validBidRequests, '0.userIdAsEids'); + if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + deepSetValue(openRtbBidRequest, 'user.eids', bidUserIdAsEids); + deepSetValue(openRtbBidRequest, 'user.ext.eids', bidUserIdAsEids); } } function buildBanner(bid) { - if (utils.deepAccess(bid, 'mediaTypes.banner')) { + if (deepAccess(bid, 'mediaTypes.banner')) { // Get Sizes from MediaTypes Object, Will always take first size, will be overrided by params for exact w,h - if (utils.deepAccess(bid, 'mediaTypes.banner.sizes') && !bid.params.height && !bid.params.width) { - let sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes'); - if (utils.isArray(sizes) && sizes.length > 0) { + if (deepAccess(bid, 'mediaTypes.banner.sizes') && !bid.params.height && !bid.params.width) { + let sizes = deepAccess(bid, 'mediaTypes.banner.sizes'); + if (isArray(sizes) && sizes.length > 0) { return { h: sizes[0][1], w: sizes[0][0], diff --git a/modules/deepintentBidAdapter.md b/modules/deepintentBidAdapter.md index 79a6a1679e2..84c375d69a4 100644 --- a/modules/deepintentBidAdapter.md +++ b/modules/deepintentBidAdapter.md @@ -8,7 +8,7 @@ Maintainer: prebid@deepintent.com # Description -Deepintent currently supports the BANNER type ads through prebid js +Deepintent currently supports the BANNER and VIDEO type ads through prebid js Module that connects to Deepintent's demand sources. @@ -40,6 +40,41 @@ Module that connects to Deepintent's demand sources. ]; ``` +# Sample Video Ad Unit +``` +var adVideoAdUnits = [ +{ + code: 'test-div-video', + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'instream' //required + } + }, + bids: [{ + bidder: 'deepintent', + params: { + tagId: '1300', // Required parameter // required + video: { + mimes: ['video/mp4','video/x-flv'], // required + skippable: true, // optional + minduration: 5, // optional + maxduration: 30, // optional + startdelay: 5, // optional + playbackmethod: [1,3], // optional + api: [ 1, 2 ], // optional + protocols: [ 2, 3 ], // optional + battr: [ 13, 14 ], // optional + linearity: 1, // optional + placement: 2, // optional + minbitrate: 10, // optional + maxbitrate: 10 // optional + } + } + }] +}] +``` + ###Recommended User Sync Configuration ```javascript diff --git a/modules/deltaprojectsBidAdapter.js b/modules/deltaprojectsBidAdapter.js new file mode 100644 index 00000000000..33df5bd252e --- /dev/null +++ b/modules/deltaprojectsBidAdapter.js @@ -0,0 +1,252 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { + _each, _map, isFn, isNumber, createTrackPixelHtml, deepAccess, parseUrl, logWarn, logError +} from '../src/utils.js'; +import {config} from '../src/config.js'; + +export const BIDDER_CODE = 'deltaprojects'; +export const BIDDER_ENDPOINT_URL = 'https://d5p.de17a.com/dogfight/prebid'; +export const USERSYNC_URL = 'https://userservice.de17a.com/getuid/prebid'; + +/** -- isBidRequestValid --**/ +function isBidRequestValid(bid) { + if (!bid) return false; + + if (bid.bidder !== BIDDER_CODE) return false; + + // publisher id is required + const publisherId = deepAccess(bid, 'params.publisherId') + if (!publisherId) { + logError('Invalid bid request, missing publisher id in params'); + return false; + } + + return true; +} + +/** -- Build requests --**/ +function buildRequests(validBidRequests, bidderRequest) { + /** == shared ==**/ + // -- build id + const id = bidderRequest.auctionId; + + // -- build site + const loc = parseUrl(bidderRequest.refererInfo.referer); + const publisherId = setOnAny(validBidRequests, 'params.publisherId'); + const siteId = setOnAny(validBidRequests, 'params.siteId'); + const site = { + id: siteId, + domain: loc.hostname, + page: loc.href, + ref: loc.href, + publisher: { id: publisherId }, + }; + + // -- build device + const ua = navigator.userAgent; + const device = { + ua, + w: screen.width, + h: screen.height + } + + // -- build user, reg + let user = { ext: {} }; + const regs = { ext: {} }; + const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + if (gdprConsent) { + user.ext = { consent: gdprConsent.consentString }; + if (typeof gdprConsent.gdprApplies == 'boolean') { + regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0 + } + } + + // -- build tmax + let tmax = (bidderRequest && bidderRequest.timeout > 0) ? bidderRequest.timeout : undefined; + + // build bid specific + return validBidRequests.map(validBidRequest => { + const openRTBRequest = buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs); + return { + method: 'POST', + url: BIDDER_ENDPOINT_URL, + data: openRTBRequest, + options: { contentType: 'application/json' }, + bids: [validBidRequest], + }; + }); +} + +function buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs) { + // build cur + const currency = config.getConfig('currency.adServerCurrency') || deepAccess(validBidRequest, 'params.currency'); + const cur = currency && [currency]; + + // build impression + const impression = buildImpression(validBidRequest, currency); + + // build test + const test = deepAccess(validBidRequest, 'params.test') ? 1 : 0 + + const at = 1 + + // build source + const source = { + tid: validBidRequest.transactionId, + fd: 1, + } + + return { + id, + at, + imp: [impression], + site, + device, + user, + test, + tmax, + cur, + source, + regs, + ext: {}, + }; +} + +function buildImpression(bid, currency) { + const impression = { + id: bid.bidId, + tagid: bid.params.tagId, + ext: {}, + }; + + const bannerMediaType = deepAccess(bid, `mediaTypes.${BANNER}`); + impression.banner = buildImpressionBanner(bid, bannerMediaType); + + // bid floor + const bidFloor = getBidFloor(bid, BANNER, '*', currency); + if (bidFloor) { + impression.bidfloor = bidFloor.floor; + impression.bidfloorcur = bidFloor.currency; + } + + return impression; +} + +function buildImpressionBanner(bid, bannerMediaType) { + const bannerSizes = (bannerMediaType && bannerMediaType.sizes) || bid.sizes; + return { + format: _map(bannerSizes, ([width, height]) => ({ w: width, h: height })), + }; +} + +/** -- Interpret response --**/ +function interpretResponse(serverResponse) { + if (!serverResponse.body) { + logWarn('Response body is invalid, return !!'); + return []; + } + + const { body: { id, seatbid, cur } } = serverResponse; + if (!id || !seatbid) { + logWarn('Id / seatbid of response is invalid, return !!'); + return []; + } + + const bidResponses = []; + + _each(seatbid, seatbid => { + _each(seatbid.bid, bid => { + const bidObj = { + requestId: bid.impid, + cpm: parseFloat(bid.price), + width: parseInt(bid.w), + height: parseInt(bid.h), + creativeId: bid.crid || bid.id, + dealId: bid.dealid || null, + currency: cur, + netRevenue: true, + ttl: 60, + }; + + bidObj.mediaType = BANNER; + bidObj.ad = bid.adm; + if (bid.nurl) { + bidObj.ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); + } + if (bid.ext) { + bidObj[BIDDER_CODE] = bid.ext; + } + bidResponses.push(bidObj); + }); + }); + return bidResponses; +} + +/** -- On Bid Won -- **/ +function onBidWon(bid) { + let cpm = bid.cpm; + if (bid.currency && bid.currency !== bid.originalCurrency && typeof bid.getCpmInNewCurrency === 'function') { + cpm = bid.getCpmInNewCurrency(bid.originalCurrency); + } + const wonPrice = Math.round(cpm * 1000000); + const wonPriceMacroPatten = /\$\{AUCTION_PRICE:B64\}/g; + bid.ad = bid.ad.replace(wonPriceMacroPatten, wonPrice); +} + +/** -- Get user syncs --**/ +function getUserSyncs(syncOptions, serverResponses, gdprConsent) { + const syncs = [] + + if (syncOptions.pixelEnabled) { + let gdprParams; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; + } + } else { + gdprParams = ''; + } + syncs.push({ + type: 'image', + url: USERSYNC_URL + gdprParams + }); + } + return syncs; +} + +/** -- Get bid floor --**/ +export function getBidFloor(bid, mediaType, size, currency) { + if (isFn(bid.getFloor)) { + const bidFloorCurrency = currency || 'USD'; + const bidFloor = bid.getFloor({currency: bidFloorCurrency, mediaType: mediaType, size: size}); + if (isNumber(bidFloor.floor)) { + return bidFloor; + } + } +} + +/** -- Helper methods --**/ +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +/** -- Register -- */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidWon, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/modules/deltaprojectsBidAdapter.md b/modules/deltaprojectsBidAdapter.md new file mode 100644 index 00000000000..97cef4dd228 --- /dev/null +++ b/modules/deltaprojectsBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: Delta Projects Bid Adapter +Module Type: Bidder Adapter +Maintainer: dev@deltaprojects.com +``` + +# Description + +Connects to Delta Projects DSP for bids. + +# Test Parameters +``` +// define banner unit +var bannerUnit = { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'deltaprojects', + params: { + publisherId: '4' //required + } + }] +}; +``` + diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 22c904b604c..79cb03ec001 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -123,7 +123,7 @@ export function notifyTranslationModule(fn) { fn.call(this, 'dfp'); } -getHook('registerAdserver').before(notifyTranslationModule); +if (config.getConfig('brandCategoryTranslation.translationFile')) { getHook('registerAdserver').before(notifyTranslationModule); } /** * @typedef {Object} DfpAdpodOptions @@ -271,7 +271,7 @@ function getCustParams(bid, options) { const prebidTargetingSet = Object.assign({}, // Why are we adding standard keys here ? Refer https://github.com/prebid/Prebid.js/issues/3664 { hb_uuid: bid && bid.videoCacheKey }, - // hb_uuid will be deprecated and replaced by hb_cache_id + // hb_cache_id became optional in prebid 5.0 after 4.x enabled the concept of optional keys. Discussion led to reversing the prior expectation of deprecating hb_uuid { hb_cache_id: bid && bid.videoCacheKey }, allTargetingData, adserverTargeting, diff --git a/modules/dgkeywordRtdProvider.js b/modules/dgkeywordRtdProvider.js new file mode 100644 index 00000000000..26a8257077a --- /dev/null +++ b/modules/dgkeywordRtdProvider.js @@ -0,0 +1,133 @@ +/** + * This module adds dgkeyword provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will get keywords from 1plux profile api. + * This module can work only with AppNexusBidAdapter. + * @module modules/dgkeywordProvider + * @requires module:modules/realTimeData + */ + +import { logMessage, deepSetValue, logError, logInfo } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getGlobal } from '../src/prebidGlobal.js'; + +/** + * get keywords from api server. and set keywords. + * @param {Object} reqBidsConfigObj + * @param {function} callback + * @param {Object} moduleConfig + * @param {Object} userConsent + */ +export function getDgKeywordsAndSet(reqBidsConfigObj, callback, moduleConfig, userConsent) { + const URL = 'https://mediaconsortium.profiles.tagger.opecloud.com/api/v1?url='; + const PROFILE_TIMEOUT_MS = 1000; + const timeout = (moduleConfig && moduleConfig.params && moduleConfig.params.timeout && Number(moduleConfig.params.timeout) > 0) ? Number(moduleConfig.params.timeout) : PROFILE_TIMEOUT_MS; + const url = (moduleConfig && moduleConfig.params && moduleConfig.params.url) ? moduleConfig.params.url : URL + encodeURIComponent(window.location.href); + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + let isFinish = false; + logMessage('[dgkeyword sub module]', adUnits, timeout); + let setKeywordTargetBidders = getTargetBidderOfDgKeywords(adUnits); + if (setKeywordTargetBidders.length <= 0) { + logMessage('[dgkeyword sub module] no dgkeyword targets.'); + callback(); + } else { + logMessage('[dgkeyword sub module] dgkeyword targets:', setKeywordTargetBidders); + logMessage('[dgkeyword sub module] get targets from profile api start.'); + ajax(url, { + success: function(response) { + const res = JSON.parse(response); + if (!isFinish) { + logMessage('[dgkeyword sub module] get targets from profile api end.'); + if (res) { + let keywords = {}; + if (res['s'] != null && res['s'].length > 0) { + keywords['opeaud'] = res['s']; + } + if (res['t'] != null && res['t'].length > 0) { + keywords['opectx'] = res['t']; + } + if (Object.keys(keywords).length > 0) { + const targetBidKeys = {} + for (let bid of setKeywordTargetBidders) { + // set keywords to params + bid.params.keywords = keywords; + if (!targetBidKeys[bid.bidder]) { + targetBidKeys[bid.bidder] = true; + } + } + + if (!reqBidsConfigObj._ignoreSetOrtb2) { + // set keywrods to ortb2 + let addOrtb2 = {}; + deepSetValue(addOrtb2, 'site.keywords', keywords); + deepSetValue(addOrtb2, 'user.keywords', keywords); + const ortb2 = {ortb2: addOrtb2}; + reqBidsConfigObj.setBidderConfig({ bidders: Object.keys(targetBidKeys), config: ortb2 }); + } + } + } + isFinish = true; + } + callback(); + }, + error: function(errorStatus) { + // error occur + logError('[dgkeyword sub module] profile api access error.', errorStatus); + callback(); + } + }, null, { + withCredentials: true, + }); + setTimeout(function () { + if (!isFinish) { + // profile api timeout + logInfo('[dgkeyword sub module] profile api timeout. [timeout: ' + timeout + 'ms]'); + isFinish = true; + } + callback(); + }, timeout); + } +} + +/** + * get all bidder which hava {dgkeyword: true} in params + * @param {Object} adUnits + */ +export function getTargetBidderOfDgKeywords(adUnits) { + let setKeywordTargetBidders = []; + for (let adUnit of adUnits) { + for (let bid of adUnit.bids) { + if (bid.params && bid.params['dgkeyword'] === true) { + delete bid.params['dgkeyword']; + setKeywordTargetBidders.push(bid); + } + } + } + return setKeywordTargetBidders; +} + +/** @type {RtdSubmodule} */ +export const dgkeywordSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: 'dgkeyword', + /** + * get data and send back to realTimeData module + * @function + * @param {string[]} adUnitsCodes + */ + getBidRequestData: getDgKeywordsAndSet, + init: init, +}; + +function init(moduleConfig) { + return true; +} + +function registerSubModule() { + submodule('realTimeData', dgkeywordSubmodule); +} +registerSubModule(); diff --git a/modules/dgkeywordRtdProvider.md b/modules/dgkeywordRtdProvider.md new file mode 100644 index 00000000000..314475b45a8 --- /dev/null +++ b/modules/dgkeywordRtdProvider.md @@ -0,0 +1,29 @@ + ## Integration + +1) Compile the Digital Garage Keyword Module and Appnexus Bid Adapter into your Prebid build: + +``` +gulp build --modules="dgkeywordRtdProvider,appnexusBidAdapter,..." +``` + +2) Use `setConfig` to instruct Prebid.js to initilize the dgkeyword module, as specified below. + +## Configuration + +This module is configured as part of the `realTimeData.dataProviders` + +```javascript +var DGKEYWORD_TIMEOUT = 1000; +pbjs.setConfig({ + realTimeData: { + auctionDelay: DGKEYWORD_TIMEOUT, + dataProviders: [{ + name: 'dgkeyword', + waitForIt: true, + params: { + timeout: DGKEYWORD_TIMEOUT + } + }] + } +}); +``` diff --git a/modules/displayioBidAdapter.js b/modules/displayioBidAdapter.js new file mode 100644 index 00000000000..55a2f4a8604 --- /dev/null +++ b/modules/displayioBidAdapter.js @@ -0,0 +1,157 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; + +const BIDDER_VERSION = '1.0.0'; +const BIDDER_CODE = 'displayio'; +const GVLID = 999; +const BID_TTL = 300; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const DEFAULT_CURRENCY = 'USD'; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.placementId && bid.params.siteId && + bid.params.adsSrvDomain && bid.params.cdnDomain); + }, + buildRequests: function (bidRequests, bidderRequest) { + return bidRequests.map(bid => { + let url = '//' + bid.params.adsSrvDomain + '/srv?method=getPlacement&app=' + + bid.params.siteId + '&placement=' + bid.params.placementId; + const data = this._getPayload(bid, bidderRequest); + return { + method: 'POST', + headers: {'Content-Type': 'application/json;charset=utf-8'}, + url, + data + }; + }); + }, + interpretResponse: function (serverResponse, serverRequest) { + const ads = serverResponse.body.data.ads; + const bidResponses = []; + const { data } = serverRequest.data; + if (ads.length) { + const adData = ads[0].ad.data; + const bidResponse = { + requestId: data.id, + cpm: adData.ecpm, + width: adData.w, + height: adData.h, + netRevenue: true, + ttl: BID_TTL, + creativeId: adData.adId || 0, + currency: DEFAULT_CURRENCY, + referrer: data.data.ref, + mediaType: ads[0].ad.subtype, + ad: adData.markup, + placement: data.placement, + adData: adData + }; + if (bidResponse.vastUrl === 'videoVast') { + bidResponse.vastUrl = adData.videos[0].url + } + bidResponses.push(bidResponse); + } + return bidResponses; + }, + _getPayload: function (bid, bidderRequest) { + const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; + const userSession = 'us_web_xxxxxxxxxxxx'.replace(/[x]/g, c => { + let r = Math.random() * 16 | 0; + let v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + const { params } = bid; + const { siteId, placementId } = params; + const { refererInfo, uspConsent, gdprConsent } = bidderRequest; + const mediation = {consent: '-1', gdpr: '-1'}; + if (gdprConsent) { + if (gdprConsent.consentString !== undefined) { + mediation.consent = gdprConsent.consentString; + } + if (gdprConsent.gdprApplies !== undefined) { + mediation.gdpr = gdprConsent.gdprApplies ? '1' : '0'; + } + } + const payload = { + userSession, + data: { + id: bid.bidId, + action: 'getPlacement', + app: siteId, + placement: placementId, + data: { + pagecat: params.pageCategory ? params.pageCategory.split(',').map(k => k.trim()) : [], + keywords: params.keywords ? params.keywords.split(',').map(k => k.trim()) : [], + lang_content: document.documentElement.lang, + lang: window.navigator.language, + domain: window.location.hostname, + page: window.location.href, + ref: refererInfo.referer, + userids: _getUserIDs(), + geo: '', + }, + complianceData: { + child: '-1', + us_privacy: uspConsent, + dnt: window.navigator.doNotTrack, + iabConsent: {}, + mediation: { + consent: mediation.consent, + gdpr: mediation.gdpr, + } + }, + integration: 'JS', + omidpn: 'Displayio', + mediationPlatform: 0, + prebidVersion: BIDDER_VERSION, + device: { + w: window.screen.width, + h: window.screen.height, + connection_type: connection ? connection.effectiveType : '', + } + } + } + if (navigator.permissions) { + navigator.permissions.query({ name: 'geolocation' }) + .then((result) => { + if (result.state === 'granted') { + payload.data.data.geo = _getGeoData(); + } + }); + } + return payload + } +}; + +function _getUserIDs () { + let ids = {}; + try { + ids = window.owpbjs.getUserIdsAsEids(); + } catch (e) {} + return ids; +} + +async function _getGeoData () { + let geoData = null; + const getCurrentPosition = () => { + return new Promise((resolve, reject) => + navigator.geolocation.getCurrentPosition(resolve, reject) + ); + } + try { + const position = await getCurrentPosition(); + let {latitude, longitude, accuracy} = position.coords; + geoData = { + 'lat': latitude, + 'lng': longitude, + 'precision': accuracy + }; + } catch (e) {} + return geoData +} + +registerBidder(spec); diff --git a/modules/displayioBidAdapter.md b/modules/displayioBidAdapter.md new file mode 100644 index 00000000000..41505ee966e --- /dev/null +++ b/modules/displayioBidAdapter.md @@ -0,0 +1,148 @@ +# Overview + +``` +Module Name: DisplayIO Bidder Adapter +Module Type: Bidder Adapter +``` + +# Description + +Module that connects to display.io's demand sources. +Web mobile (not relevant for web desktop). + + +#Features +| Feature | | Feature | | +|---------------|---------------------------------------------------------|-----------------------|-----| +| Bidder Code | displayio | Prebid member | no | +| Media Types | Banner, video.
Sizes (display 320x480 / vertical video) | GVL ID | no | +| GDPR Support | yes | Prebid.js Adapter | yes | +| USP Support | yes | Prebid Server Adapter | no | + + +#Global configuration +```javascript + + + +`; -} - -function getCreativeSize(creativeSize) { - const size = { - width: 1, - height: 1, - }; - - if (!!creativeSize.width && creativeSize.width !== -1) { - size.width = creativeSize.width; - } - if (!!creativeSize.height && creativeSize.height !== -1) { - size.height = creativeSize.height; - } - - return size; -} - -function generateRandomInt() { - return Math.random().toString(16).substring(2); -} - -registerBidder(spec); diff --git a/modules/hybridBidAdapter.js b/modules/hybridBidAdapter.js index e7281086a92..4383e62c16e 100644 --- a/modules/hybridBidAdapter.js +++ b/modules/hybridBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js' +import { _map, logWarn, deepAccess, isArray } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import { auctionManager } from '../src/auctionManager.js' import { BANNER, VIDEO } from '../src/mediaTypes.js' @@ -21,7 +21,7 @@ const placementTypes = { }; function buildBidRequests(validBidRequests) { - return utils._map(validBidRequests, function(validBidRequest) { + return _map(validBidRequests, function(validBidRequest) { const params = validBidRequest.params; const bidRequest = { bidId: validBidRequest.bidId, @@ -63,7 +63,7 @@ const createRenderer = (bid) => { try { renderer.setRender(outstreamRender); } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); + logWarn('Prebid Error calling setRender on renderer', err); } return renderer; @@ -78,7 +78,10 @@ function buildBid(bidData) { creativeId: bidData.bidId, currency: bidData.currency, netRevenue: true, - ttl: TTL + ttl: TTL, + meta: { + advertiserDomains: bidData.advertiserDomains || [], + } }; if (bidData.placement === PLACEMENT_TYPE_VIDEO) { @@ -140,8 +143,8 @@ function hasVideoMandatoryParams(mediaTypes) { const isHasVideoContext = !!mediaTypes.video && (mediaTypes.video.context === 'instream' || mediaTypes.video.context === 'outstream'); const isPlayerSize = - !!utils.deepAccess(mediaTypes, 'video.playerSize') && - utils.isArray(utils.deepAccess(mediaTypes, 'video.playerSize')); + !!deepAccess(mediaTypes, 'video.playerSize') && + isArray(deepAccess(mediaTypes, 'video.playerSize')); return isHasVideoContext && isPlayerSize; } @@ -234,8 +237,8 @@ export const spec = { let bidRequests = JSON.parse(bidRequest.data).bidRequests; const serverBody = serverResponse.body; - if (serverBody && serverBody.bids && utils.isArray(serverBody.bids)) { - return utils._map(serverBody.bids, function(bid) { + if (serverBody && serverBody.bids && isArray(serverBody.bids)) { + return _map(serverBody.bids, function(bid) { let rawBid = find(bidRequests, function (item) { return item.bidId === bid.bidId; }); diff --git a/modules/iasBidAdapter.js b/modules/iasBidAdapter.js deleted file mode 100644 index 733c123e769..00000000000 --- a/modules/iasBidAdapter.js +++ /dev/null @@ -1,134 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'ias'; - -const otherBidIds = []; - -function isBidRequestValid(bid) { - const { pubId, adUnitPath } = bid.params; - return !!(pubId && adUnitPath); -} - -/** - * Converts GPT-style size array into a string - * @param {Array} sizes: list of GPT-style sizes, e.g. [[300, 250], [300, 300]] - * @return {String} a string containing sizes, e.g. '[300.250,300.300]' - */ -function stringifySlotSizes(sizes) { - let result = ''; - if (utils.isArray(sizes)) { - result = sizes.reduce((acc, size) => { - acc.push(size.join('.')); - return acc; - }, []); - result = '[' + result.join(',') + ']'; - } - return result; -} - -function stringifySlot(bidRequest) { - const id = bidRequest.adUnitCode; - const ss = stringifySlotSizes(bidRequest.sizes); - const p = bidRequest.params.adUnitPath; - const slot = { id, ss, p }; - const keyValues = utils.getKeys(slot).map(function(key) { - return [key, slot[key]].join(':'); - }); - return '{' + keyValues.join(',') + '}'; -} - -function stringifyWindowSize() { - return [ window.innerWidth || -1, window.innerHeight || -1 ].join('.'); -} - -function stringifyScreenSize() { - return [ (window.screen && window.screen.width) || -1, (window.screen && window.screen.height) || -1 ].join('.'); -} - -function buildRequests(bidRequests) { - const IAS_HOST = 'https://pixel.adsafeprotected.com/services/pub'; - const anId = bidRequests[0].params.pubId; - - let queries = []; - queries.push(['anId', anId]); - queries = queries.concat(bidRequests.reduce(function(acc, request) { - acc.push(['slot', stringifySlot(request)]); - return acc; - }, [])); - - queries.push(['wr', stringifyWindowSize()]); - queries.push(['sr', stringifyScreenSize()]); - queries.push(['url', encodeURIComponent(window.location.href)]); - - const queryString = encodeURI(queries.map(qs => qs.join('=')).join('&')); - - bidRequests.forEach(function (request) { - if (bidRequests[0].bidId != request.bidId) { - otherBidIds.push(request.bidId); - } - }); - - return { - method: 'GET', - url: IAS_HOST, - data: queryString, - bidRequest: bidRequests[0] - }; -} - -function getPageLevelKeywords(response) { - let result = {}; - shallowMerge(result, response.brandSafety); - result.fr = response.fr; - result.custom = response.custom; - return result; -} - -function shallowMerge(dest, src) { - utils.getKeys(src).reduce((dest, srcKey) => { - dest[srcKey] = src[srcKey]; - return dest; - }, dest); -} - -function interpretResponse(serverResponse, request) { - const iasResponse = serverResponse.body; - const bidResponses = []; - - // Keys in common bid response are not used; - // Necessary to get around with prebid's common bid response check - const commonBidResponse = { - requestId: request.bidRequest.bidId, - cpm: 0.01, - width: 100, - height: 200, - creativeId: 434, - dealId: 42, - currency: 'USD', - netRevenue: true, - ttl: 360 - }; - - shallowMerge(commonBidResponse, getPageLevelKeywords(iasResponse)); - commonBidResponse.slots = iasResponse.slots; - bidResponses.push(commonBidResponse); - - otherBidIds.forEach(function (bidId) { - var otherResponse = Object.assign({}, commonBidResponse); - otherResponse.requestId = bidId; - bidResponses.push(otherResponse); - }); - - return bidResponses; -} - -export const spec = { - code: BIDDER_CODE, - aliases: [], - isBidRequestValid: isBidRequestValid, - buildRequests: buildRequests, - interpretResponse: interpretResponse -}; - -registerBidder(spec); diff --git a/modules/iasBidAdapter.md b/modules/iasBidAdapter.md deleted file mode 100644 index 3224fbf4a26..00000000000 --- a/modules/iasBidAdapter.md +++ /dev/null @@ -1,30 +0,0 @@ -# Overview - -``` -Module Name: Integral Ad Science(IAS) Bidder Adapter -Module Type: Bidder Adapter -Maintainer: kat@integralads.com -``` - -# Description - -This module is an integration with prebid.js with an IAS product, pet.js. It is not a bidder per se but works in a similar way: retrieve data that publishers might be interested in setting keyword targeting. - -# Test Parameters -``` - var adUnits = [ - { - code: 'ias-dfp-test-async', - sizes: [[300, 250]], // a display size - bids: [ - { - bidder: "ias", - params: { - pubId: '99', - adUnitPath: '/57514611/news.com' - } - } - ] - } - ]; -``` diff --git a/modules/iasRtdProvider.js b/modules/iasRtdProvider.js new file mode 100644 index 00000000000..6f7b2d5215d --- /dev/null +++ b/modules/iasRtdProvider.js @@ -0,0 +1,160 @@ +import { submodule } from '../src/hook.js'; +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { getGlobal } from '../src/prebidGlobal.js'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'ias'; +const IAS_HOST = 'https://pixel.adsafeprotected.com/services/pub'; +export let iasTargeting = {}; +const BRAND_SAFETY_OBJECT_FIELD_NAME = 'brandSafety'; +const FRAUD_FIELD_NAME = 'fr'; +const SLOTS_OBJECT_FIELD_NAME = 'slots'; +const CUSTOM_FIELD_NAME = 'custom'; +const IAS_KW = 'ias-kw'; + +/** + * Module init + * @param {Object} provider + * @param {Object} userConsent + * @return {boolean} + */ +export function init(config, userConsent) { + const params = config.params; + if (!params || !params.pubId) { + utils.logError('missing pubId param for IAS provider'); + return false; + } + return true; +} + +function stringifySlotSizes(sizes) { + let result = ''; + if (utils.isArray(sizes)) { + result = sizes.reduce((acc, size) => { + acc.push(size.join('.')); + return acc; + }, []); + result = '[' + result.join(',') + ']'; + } + return result; +} + +function stringifySlot(bidRequest) { + const sizes = utils.getAdUnitSizes(bidRequest); + const id = bidRequest.code; + const ss = stringifySlotSizes(sizes); + const adSlot = utils.getGptSlotInfoForAdUnitCode(bidRequest.code); + const p = utils.isEmpty(adSlot) ? bidRequest.code : adSlot.gptSlot; + const slot = { id, ss, p }; + const keyValues = utils.getKeys(slot).map(function (key) { + return [key, slot[key]].join(':'); + }); + return '{' + keyValues.join(',') + '}'; +} + +function stringifyWindowSize() { + return [window.innerWidth || -1, window.innerHeight || -1].join('.'); +} + +function stringifyScreenSize() { + return [(window.screen && window.screen.width) || -1, (window.screen && window.screen.height) || -1].join('.'); +} + +function formatTargetingData(adUnit) { + let result = {}; + if (iasTargeting[BRAND_SAFETY_OBJECT_FIELD_NAME]) { + utils.mergeDeep(result, iasTargeting[BRAND_SAFETY_OBJECT_FIELD_NAME]); + } + if (iasTargeting[FRAUD_FIELD_NAME]) { + result[FRAUD_FIELD_NAME] = iasTargeting[FRAUD_FIELD_NAME]; + } + if (iasTargeting[CUSTOM_FIELD_NAME] && IAS_KW in iasTargeting[CUSTOM_FIELD_NAME]) { + result[IAS_KW] = iasTargeting[CUSTOM_FIELD_NAME][IAS_KW]; + } + if (iasTargeting[SLOTS_OBJECT_FIELD_NAME] && adUnit in iasTargeting[SLOTS_OBJECT_FIELD_NAME]) { + utils.mergeDeep(result, iasTargeting[SLOTS_OBJECT_FIELD_NAME][adUnit]); + } + return result; +} + +function constructQueryString(anId, adUnits) { + let queries = []; + queries.push(['anId', anId]); + + queries = queries.concat(adUnits.reduce(function (acc, request) { + acc.push(['slot', stringifySlot(request)]); + return acc; + }, [])); + + queries.push(['wr', stringifyWindowSize()]); + queries.push(['sr', stringifyScreenSize()]); + queries.push(['url', encodeURIComponent(window.location.href)]); + + return encodeURI(queries.map(qs => qs.join('=')).join('&')); +} + +function parseResponse(result) { + let iasResponse = {}; + try { + iasResponse = JSON.parse(result); + } catch (err) { + utils.logError('error', err); + } + iasTargeting = iasResponse; +} + +function getTargetingData(adUnits, config, userConsent) { + const targeting = {}; + try { + if (!utils.isEmpty(iasTargeting)) { + adUnits.forEach(function (adUnit) { + targeting[adUnit] = formatTargetingData(adUnit); + }); + } + } catch (err) { + utils.logError('error', err); + } + utils.logInfo('IAS targeting', targeting); + return targeting; +} + +export function getApiCallback() { + return { + success: function (response, req) { + if (req.status === 200) { + try { + parseResponse(response); + } catch (e) { + utils.logError('Unable to parse IAS response.', e); + } + } + }, + error: function () { + utils.logError('failed to retrieve IAS data'); + } + } +} + +function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + const { pubId } = config.params; + const queryString = constructQueryString(pubId, adUnits); + ajax( + `${IAS_HOST}?${queryString}`, + getApiCallback(), + undefined, + { method: 'GET' } + ); +} + +/** @type {RtdSubmodule} */ +export const iasSubModule = { + name: SUBMODULE_NAME, + init: init, + getTargetingData: getTargetingData, + getBidRequestData: getBidRequestData +}; + +submodule(MODULE_NAME, iasSubModule); diff --git a/modules/iasRtdProvider.md b/modules/iasRtdProvider.md new file mode 100644 index 00000000000..d8c46ff2697 --- /dev/null +++ b/modules/iasRtdProvider.md @@ -0,0 +1,9 @@ +# Overview + +Module Name: Integral Ad Science(IAS) Rtd Provider +Module Type: Rtd Provider +Maintainer: raguilar@integralads.com + +# Description + +RTD provider for Integral Ad Science(IAS) Contact raguilar@integralads.com for information. diff --git a/modules/id5AnalyticsAdapter.js b/modules/id5AnalyticsAdapter.js new file mode 100644 index 00000000000..d2803aa3102 --- /dev/null +++ b/modules/id5AnalyticsAdapter.js @@ -0,0 +1,311 @@ +import buildAdapter from '../src/AnalyticsAdapter.js'; +import CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager.js'; +import { ajax } from '../src/ajax.js'; +import { logInfo, logError } from '../src/utils.js'; +import events from '../src/events.js'; + +const { + EVENTS: { + AUCTION_END, + TCF2_ENFORCEMENT, + BID_WON, + BID_VIEWABLE, + AD_RENDER_FAILED + } +} = CONSTANTS + +const GVLID = 131; + +const STANDARD_EVENTS_TO_TRACK = [ + AUCTION_END, + TCF2_ENFORCEMENT, + BID_WON, +]; + +// These events cause the buffered events to be sent over +const FLUSH_EVENTS = [ + TCF2_ENFORCEMENT, + AUCTION_END, + BID_WON, + BID_VIEWABLE, + AD_RENDER_FAILED +]; + +const CONFIG_URL_PREFIX = 'https://api.id5-sync.com/analytics' +const TZ = new Date().getTimezoneOffset(); +const PBJS_VERSION = $$PREBID_GLOBAL$$.version; +const ID5_REDACTED = '__ID5_REDACTED__'; +const isArray = Array.isArray; + +let id5Analytics = Object.assign(buildAdapter({analyticsType: 'endpoint'}), { + // Keeps an array of events for each auction + eventBuffer: {}, + + eventsToTrack: STANDARD_EVENTS_TO_TRACK, + + track: (event) => { + const _this = id5Analytics; + + if (!event || !event.args) { + return; + } + + try { + const auctionId = event.args.auctionId; + _this.eventBuffer[auctionId] = _this.eventBuffer[auctionId] || []; + + // Collect events and send them in a batch when the auction ends + const que = _this.eventBuffer[auctionId]; + que.push(_this.makeEvent(event.eventType, event.args)); + + if (FLUSH_EVENTS.indexOf(event.eventType) >= 0) { + // Auction ended. Send the batch of collected events + _this.sendEvents(que); + + // From now on just send events to server side as they come + que.push = (pushedEvent) => _this.sendEvents([pushedEvent]); + } + } catch (error) { + logError('id5Analytics: ERROR', error); + _this.sendErrorEvent(error); + } + }, + + sendEvents: (eventsToSend) => { + const _this = id5Analytics; + // By giving some content this will be automatically a POST + eventsToSend.forEach((event) => + ajax(_this.options.ingestUrl, null, JSON.stringify(event))); + }, + + makeEvent: (event, payload) => { + const _this = id5Analytics; + const filteredPayload = deepTransformingClone(payload, + transformFnFromCleanupRules(event)); + return { + source: 'pbjs', + event, + payload: filteredPayload, + partnerId: _this.options.partnerId, + meta: { + sampling: _this.options.id5Sampling, + pbjs: PBJS_VERSION, + tz: TZ, + } + }; + }, + + sendErrorEvent: (error) => { + const _this = id5Analytics; + _this.sendEvents([ + _this.makeEvent('analyticsError', { + message: error.message, + stack: error.stack, + }) + ]); + }, + + random: () => Math.random(), +}); + +const ENABLE_FUNCTION = (config) => { + const _this = id5Analytics; + _this.options = (config && config.options) || {}; + + const partnerId = _this.options.partnerId; + if (typeof partnerId !== 'number') { + logError('id5Analytics: partnerId in config.options must be a number representing the id5 partner ID'); + return; + } + + ajax(`${CONFIG_URL_PREFIX}/${partnerId}/pbjs`, (result) => { + logInfo('id5Analytics: Received from configuration endpoint', result); + + const configFromServer = JSON.parse(result); + + const sampling = _this.options.id5Sampling = + typeof configFromServer.sampling === 'number' ? configFromServer.sampling : 0; + + if (typeof configFromServer.ingestUrl !== 'string') { + logError('id5Analytics: cannot find ingestUrl in config endpoint response; no analytics will be available'); + return; + } + _this.options.ingestUrl = configFromServer.ingestUrl; + + // 3-way fallback for which events to track: server > config > standard + _this.eventsToTrack = configFromServer.eventsToTrack || _this.options.eventsToTrack || STANDARD_EVENTS_TO_TRACK; + _this.eventsToTrack = isArray(_this.eventsToTrack) ? _this.eventsToTrack : STANDARD_EVENTS_TO_TRACK; + + logInfo('id5Analytics: Configuration is', _this.options); + logInfo('id5Analytics: Tracking events', _this.eventsToTrack); + if (sampling > 0 && _this.random() < (1 / sampling)) { + // Init the module only if we got lucky + logInfo('id5Analytics: Selected by sampling. Starting up!') + + // Clean start + _this.eventBuffer = {}; + + // Replay all events until now + if (!config.disablePastEventsProcessing) { + events.getEvents().forEach((event) => { + if (event && _this.eventsToTrack.indexOf(event.eventType) >= 0) { + _this.track(event); + } + }); + } + + // Merge in additional cleanup rules + if (configFromServer.additionalCleanupRules) { + const newRules = configFromServer.additionalCleanupRules; + _this.eventsToTrack.forEach((key) => { + // Some protective checks in case we mess up server side + if ( + isArray(newRules[key]) && + newRules[key].every((eventRules) => + isArray(eventRules.match) && + (eventRules.apply in TRANSFORM_FUNCTIONS)) + ) { + logInfo('id5Analytics: merging additional cleanup rules for event ' + key); + CLEANUP_RULES[key].push(...newRules[key]); + } + }); + } + + // Register to the events of interest + _this.handlers = {}; + _this.eventsToTrack.forEach((eventType) => { + const handler = _this.handlers[eventType] = (args) => + _this.track({ eventType, args }); + events.on(eventType, handler); + }); + } + }); + + // Make only one init possible within a lifecycle + _this.enableAnalytics = () => {}; +}; + +id5Analytics.enableAnalytics = ENABLE_FUNCTION; +id5Analytics.disableAnalytics = () => { + const _this = id5Analytics; + // Un-register to the events of interest + _this.eventsToTrack.forEach((eventType) => { + if (_this.handlers && _this.handlers[eventType]) { + events.off(eventType, _this.handlers[eventType]); + } + }); + + // Make re-init possible. Work around the fact that past events cannot be forgotten + _this.enableAnalytics = (config) => { + config.disablePastEventsProcessing = true; + ENABLE_FUNCTION(config); + }; +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: id5Analytics, + code: 'id5Analytics', + gvlid: GVLID +}); + +export default id5Analytics; + +function redact(obj, key) { + obj[key] = ID5_REDACTED; +} + +function erase(obj, key) { + delete obj[key]; +} + +// The transform function matches against a path and applies +// required transformation if match is found. +function deepTransformingClone(obj, transform, currentPath = []) { + const result = isArray(obj) ? [] : {}; + const recursable = typeof obj === 'object' && obj !== null; + if (recursable) { + const keys = Object.keys(obj); + if (keys.length > 0) { + keys.forEach((key) => { + const newPath = currentPath.concat(key); + result[key] = deepTransformingClone(obj[key], transform, newPath); + transform(newPath, result, key); + }); + return result; + } + } + return obj; +} + +// Every set of rules is an object where "match" is an array and +// "apply" is the function to apply in case of match. The function to apply +// takes (obj, prop) and transforms property "prop" in object "obj". +// The "match" is an array of path parts. Each part is either a string or an array. +// In case of array, it represents alternatives which all would match. +// Special path part '*' matches any subproperty or array index. +// Prefixing a part with "!" makes it negative match (doesn't work with multiple alternatives) +const CLEANUP_RULES = {}; +CLEANUP_RULES[AUCTION_END] = [{ + match: [['adUnits', 'bidderRequests'], '*', 'bids', '*', ['userId', 'crumbs'], '!id5id'], + apply: 'redact' +}, { + match: [['adUnits', 'bidderRequests'], '*', 'bids', '*', ['userId', 'crumbs'], 'id5id', 'uid'], + apply: 'redact' +}, { + match: [['adUnits', 'bidderRequests'], '*', 'bids', '*', 'userIdAsEids', '*', 'uids', '*', ['id', 'ext']], + apply: 'redact' +}, { + match: ['bidderRequests', '*', 'gdprConsent', 'vendorData'], + apply: 'erase' +}, { + match: ['bidsReceived', '*', ['ad', 'native']], + apply: 'erase' +}, { + match: ['noBids', '*', ['userId', 'crumbs'], '*'], + apply: 'redact' +}, { + match: ['noBids', '*', 'userIdAsEids', '*', 'uids', '*', ['id', 'ext']], + apply: 'redact' +}]; + +CLEANUP_RULES[BID_WON] = [{ + match: [['ad', 'native']], + apply: 'erase' +}]; + +const TRANSFORM_FUNCTIONS = { + 'redact': redact, + 'erase': erase, +}; + +// Builds a rule function depending on the event type +function transformFnFromCleanupRules(eventType) { + const rules = CLEANUP_RULES[eventType] || []; + return (path, obj, key) => { + for (let i = 0; i < rules.length; i++) { + let match = true; + const ruleMatcher = rules[i].match; + const transformation = rules[i].apply; + if (ruleMatcher.length !== path.length) { + continue; + } + for (let fragment = 0; fragment < ruleMatcher.length && match; fragment++) { + const choices = makeSureArray(ruleMatcher[fragment]); + match = !choices.every((choice) => choice !== '*' && + (choice.charAt(0) === '!' + ? path[fragment] === choice.substring(1) + : path[fragment] !== choice)); + } + if (match) { + const transformfn = TRANSFORM_FUNCTIONS[transformation]; + transformfn(obj, key); + break; + } + } + }; +} + +function makeSureArray(object) { + return isArray(object) ? object : [object]; +} diff --git a/modules/id5AnalyticsAdapter.md b/modules/id5AnalyticsAdapter.md new file mode 100644 index 00000000000..e12784b70f6 --- /dev/null +++ b/modules/id5AnalyticsAdapter.md @@ -0,0 +1,42 @@ +# Overview +Module Name: ID5 Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: [id5.io](https://id5.io) + +# ID5 Universal ID + +The ID5 Universal ID is a shared, neutral identifier that publishers and ad tech platforms can use to recognise users even in environments where 3rd party cookies are not available. The ID5 Universal ID is designed to respect users' privacy choices and publishers’ preferences throughout the advertising value chain. For more information about the ID5 Universal ID and detailed integration docs, please visit [our documentation](https://support.id5.io/portal/en/kb/articles/prebid-js-user-id-module). + +# ID5 Analytics Registration + +The ID5 Analytics Adapter is free to use during our Beta period, but requires a simple registration with ID5. Please visit [id5.io/universal-id](https://id5.io/universal-id) to sign up and request your ID5 Partner Number to get started. If you're already using the ID5 Universal ID, you may use your existing Partner Number with the analytics adapter. + +The ID5 privacy policy is at [https://www.id5.io/platform-privacy-policy](https://www.id5.io/platform-privacy-policy). + +## ID5 Analytics Configuration + +First, make sure to add the ID5 Analytics submodule to your Prebid.js package with: + +``` +gulp build --modules=...,id5AnalyticsAdapter +``` + +The following configuration parameters are available: + +```javascript +pbjs.enableAnalytics({ + provider: 'id5Analytics', + options: { + partnerId: 1234, // change to the Partner Number you received from ID5 + eventsToTrack: ['auctionEnd','bidWon'] + } +}); +``` + +| Parameter | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| provider | Required | String | The name of this module: `id5Analytics` | `id5Analytics` | +| options.partnerId | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `1234` | +| options.eventsToTrack | Optional | Array of strings | Overrides the set of tracked events | `['auctionEnd','bidWon']` | diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 8a8cd25479f..43d26224164 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -5,7 +5,7 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js' +import { deepAccess, logInfo, deepSetValue, logError, isEmpty, isEmptyStr, logWarn } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getRefererInfo } from '../src/refererDetection.js'; @@ -18,7 +18,6 @@ const NB_EXP_DAYS = 30; export const ID5_STORAGE_NAME = 'id5id'; export const ID5_PRIVACY_STORAGE_NAME = `${ID5_STORAGE_NAME}_privacy`; const LOCAL_STORAGE = 'html5'; -const ABTEST_RESOLUTION = 10000; const LOG_PREFIX = 'User ID - ID5 submodule: '; // order the legacy cookie names in reverse priority order so the last @@ -59,22 +58,6 @@ export const id5IdSubmodule = { return undefined; } - // check for A/B testing configuration and hide ID if in Control Group - const abConfig = getAbTestingConfig(config); - const controlGroup = isInControlGroup(universalUid, abConfig.controlGroupPct); - if (abConfig.enabled === true && typeof controlGroup === 'undefined') { - // A/B Testing is enabled, but configured improperly, so skip A/B testing - utils.logError(LOG_PREFIX + 'A/B Testing controlGroupPct must be a number >= 0 and <= 1! Skipping A/B Testing'); - } else if (abConfig.enabled === true && controlGroup === true) { - // A/B Testing is enabled and user is in the Control Group, so do not share the ID5 ID - utils.logInfo(LOG_PREFIX + 'A/B Testing Enabled - user is in the Control Group, so the ID5 ID is NOT exposed'); - universalUid = ''; - linkType = 0; - } else if (abConfig.enabled === true) { - // A/B Testing is enabled but user is not in the Control Group, so ID5 ID is shared - utils.logInfo(LOG_PREFIX + 'A/B Testing Enabled - user is NOT in the Control Group, so the ID5 ID is exposed'); - } - let responseObj = { id5id: { uid: universalUid, @@ -84,11 +67,25 @@ export const id5IdSubmodule = { } }; - if (abConfig.enabled === true) { - utils.deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', (typeof controlGroup === 'undefined' ? false : controlGroup)); + const abTestingResult = deepAccess(value, 'ab_testing.result'); + switch (abTestingResult) { + case 'control': + // A/B Testing is enabled and user is in the Control Group + logInfo(LOG_PREFIX + 'A/B Testing - user is in the Control Group: ID5 ID is NOT exposed'); + deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', true); + break; + case 'error': + // A/B Testing is enabled, but configured improperly, so skip A/B testing + logError(LOG_PREFIX + 'A/B Testing ERROR! controlGroupPct must be a number >= 0 and <= 1'); + break; + case 'normal': + // A/B Testing is enabled but user is not in the Control Group, so ID5 ID is shared + logInfo(LOG_PREFIX + 'A/B Testing - user is NOT in the Control Group'); + deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', false); + break; } - utils.logInfo(LOG_PREFIX + 'Decoded ID', responseObj); + logInfo(LOG_PREFIX + 'Decoded ID', responseObj); return responseObj; }, @@ -123,25 +120,28 @@ export const id5IdSubmodule = { }; // pass in optional data, but only if populated - if (hasGdpr && typeof consentData.consentString !== 'undefined' && !utils.isEmpty(consentData.consentString) && !utils.isEmptyStr(consentData.consentString)) { + if (hasGdpr && typeof consentData.consentString !== 'undefined' && !isEmpty(consentData.consentString) && !isEmptyStr(consentData.consentString)) { data.gdpr_consent = consentData.consentString; } - if (typeof usp !== 'undefined' && !utils.isEmpty(usp) && !utils.isEmptyStr(usp)) { + if (typeof usp !== 'undefined' && !isEmpty(usp) && !isEmptyStr(usp)) { data.us_privacy = usp; } - if (typeof signature !== 'undefined' && !utils.isEmptyStr(signature)) { + if (typeof signature !== 'undefined' && !isEmptyStr(signature)) { data.s = signature; } - if (typeof config.params.pd !== 'undefined' && !utils.isEmptyStr(config.params.pd)) { + if (typeof config.params.pd !== 'undefined' && !isEmptyStr(config.params.pd)) { data.pd = config.params.pd; } - if (typeof config.params.provider !== 'undefined' && !utils.isEmptyStr(config.params.provider)) { + if (typeof config.params.provider !== 'undefined' && !isEmptyStr(config.params.provider)) { data.provider = config.params.provider; } - // pass in feature flags, if applicable - if (getAbTestingConfig(config).enabled === true) { - utils.deepSetValue(data, 'features.ab', 1); + const abTestingConfig = getAbTestingConfig(config); + if (abTestingConfig.enabled === true) { + data.ab_testing = { + enabled: true, + control_group_pct: abTestingConfig.controlGroupPct // The server validates + }; } const resp = function (callback) { @@ -151,7 +151,7 @@ export const id5IdSubmodule = { if (response) { try { responseObj = JSON.parse(response); - utils.logInfo(LOG_PREFIX + 'response received from the server', responseObj); + logInfo(LOG_PREFIX + 'response received from the server', responseObj); resetNb(config.params.partner); @@ -165,20 +165,20 @@ export const id5IdSubmodule = { removeLegacyCookies(config.params.partner); } } catch (error) { - utils.logError(LOG_PREFIX + error); + logError(LOG_PREFIX + error); } } callback(responseObj); }, error: error => { - utils.logError(LOG_PREFIX + 'getId fetch encountered an error', error); + logError(LOG_PREFIX + 'getId fetch encountered an error', error); callback(); } }; - utils.logInfo(LOG_PREFIX + 'requesting an ID from the server', data); + logInfo(LOG_PREFIX + 'requesting an ID from the server', data); ajax(url, callbacks, JSON.stringify(data), { method: 'POST', withCredentials: true }); }; - return {callback: resp}; + return { callback: resp }; }, /** @@ -198,29 +198,29 @@ export const id5IdSubmodule = { const partnerId = (config && config.params && config.params.partner) || 0; incrementNb(partnerId); - utils.logInfo(LOG_PREFIX + 'using cached ID', cacheIdObj); + logInfo(LOG_PREFIX + 'using cached ID', cacheIdObj); return cacheIdObj; } }; function hasRequiredConfig(config) { if (!config || !config.params || !config.params.partner || typeof config.params.partner !== 'number') { - utils.logError(LOG_PREFIX + 'partner required to be defined as a number'); + logError(LOG_PREFIX + 'partner required to be defined as a number'); return false; } if (!config.storage || !config.storage.type || !config.storage.name) { - utils.logError(LOG_PREFIX + 'storage required to be set'); + logError(LOG_PREFIX + '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 + `storage type recommended to be '${LOCAL_STORAGE}'. In a future release this may become a strict requirement`); + logWarn(LOG_PREFIX + `storage type recommended to be '${LOCAL_STORAGE}'. In a future release this may become a strict requirement`); } // in a future release, we may return false if storage type or name are not set as required if (config.storage.name !== ID5_STORAGE_NAME) { - utils.logWarn(LOG_PREFIX + `storage name recommended to be '${ID5_STORAGE_NAME}'. In a future release this may become a strict requirement`); + logWarn(LOG_PREFIX + `storage name recommended to be '${ID5_STORAGE_NAME}'. In a future release this may become a strict requirement`); } return true; @@ -265,7 +265,7 @@ function getLegacyCookieSignature() { * @param {integer} partnerId */ function removeLegacyCookies(partnerId) { - utils.logInfo(LOG_PREFIX + 'removing legacy cookies'); + logInfo(LOG_PREFIX + 'removing legacy cookies'); LEGACY_COOKIE_NAMES.forEach(function(cookie) { storage.setCookie(`${cookie}`, ' ', expDaysStr(-1)); storage.setCookie(`${cookie}_nb`, ' ', expDaysStr(-1)); @@ -310,37 +310,10 @@ export function storeInLocalStorage(key, value, expDays) { * gets the existing abTesting config or generates a default config with abTesting off * * @param {SubmoduleConfig|undefined} config - * @returns {(Object|undefined)} + * @returns {Object} an object which always contains at least the property "enabled" */ function getAbTestingConfig(config) { - return (config && config.params && config.params.abTesting) || { enabled: false }; -} - -/** - * Return a consistant random number between 0 and ABTEST_RESOLUTION-1 for this user - * Falls back to plain random if no user provided - * @param {string} userId - * @returns {number} - */ -function abTestBucket(userId) { - if (userId) { - return ((utils.cyrb53Hash(userId) % ABTEST_RESOLUTION) + ABTEST_RESOLUTION) % ABTEST_RESOLUTION; - } else { - return Math.floor(Math.random() * ABTEST_RESOLUTION); - } -} - -/** - * Return a consistant boolean if this user is within the control group ratio provided - * @param {string} userId - * @param {number} controlGroupPct - Ratio [0,1] of users expected to be in the control group - * @returns {boolean} - */ -export function isInControlGroup(userId, controlGroupPct) { - if (!utils.isNumber(controlGroupPct) || controlGroupPct < 0 || controlGroupPct > 1) { - return undefined; - } - return abTestBucket(userId) < controlGroupPct * ABTEST_RESOLUTION; + return deepAccess(config, 'params.abTesting', { enabled: false }); } submodule('userId', id5IdSubmodule); diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 6a662361492..11f8ffc5609 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -1,14 +1,14 @@ # ID5 Universal ID -The ID5 Universal ID is a shared, neutral identifier that publishers and ad tech platforms can use to recognise users even in environments where 3rd party cookies are not available. The ID5 Universal ID is designed to respect users' privacy choices and publishers’ preferences throughout the advertising value chain. For more information about the ID5 Universal ID and detailed integration docs, please visit [our documentation](https://wiki.id5.io/x/BIAZ). We also recommend that you sign up for our [release notes](https://id5.io/universal-id/release-notes) to stay up-to-date with any changes to the implementation of the ID5 Universal ID in Prebid. +The ID5 ID is a shared, neutral identifier that publishers and ad tech platforms can use to recognise users even in environments where 3rd party cookies are not available. The ID5 ID is designed to respect users' privacy choices and publishers’ preferences throughout the advertising value chain. For more information about the ID5 ID and detailed integration docs, please visit [our documentation](https://support.id5.io/portal/en/kb/articles/prebid-js-user-id-module). -## ID5 Universal ID Registration +## ID5 ID Registration -The ID5 Universal ID is free to use, but requires a simple registration with ID5. Please visit [id5.io/universal-id](https://id5.io/universal-id) to sign up and request your ID5 Partner Number to get started. +The ID5 ID is free to use, but requires a simple registration with ID5. Please visit [our website](https://id5.io/solutions/#publishers) to sign up and request your ID5 Partner Number to get started. -The ID5 privacy policy is at [https://www.id5.io/platform-privacy-policy](https://www.id5.io/platform-privacy-policy). +The ID5 privacy policy is at [https://id5.io/platform-privacy-policy](https://id5.io/platform-privacy-policy). -## ID5 Universal ID Configuration +## ID5 ID Configuration First, make sure to add the ID5 submodule to your Prebid.js package with: @@ -46,9 +46,9 @@ pbjs.setConfig({ | Param under userSync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | -| params | Required | Object | Details for the ID5 Universal ID. | | +| params | Required | Object | Details for the ID5 ID. | | | params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | -| params.pd | Optional | String | Publisher-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/x/BIAZ) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | +| params.pd | Optional | String | Partner-supplied data used for linking ID5 IDs across domains. See [our documentation](https://support.id5.io/portal/en/kb/articles/passing-partner-data-to-id5) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | | params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | | params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | | params.abTesting.enabled | Optional | Boolean | Set this to `true` to turn on this feature | `true` or `false` | diff --git a/modules/idImportLibrary.js b/modules/idImportLibrary.js index 2a3a86cf270..e1847edfce4 100644 --- a/modules/idImportLibrary.js +++ b/modules/idImportLibrary.js @@ -1,7 +1,7 @@ +import { logInfo, logError } from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {ajax} from '../src/ajax.js'; import {config} from '../src/config.js'; -import * as utils from '../src/utils.js'; import MD5 from 'crypto-js/md5.js'; let email; @@ -9,6 +9,7 @@ let conf; const LOG_PRE_FIX = 'ID-Library: '; const CONF_DEFAULT_OBSERVER_DEBOUNCE_MS = 250; const CONF_DEFAULT_FULL_BODY_SCAN = false; +const CONF_DEFAULT_INPUT_SCAN = false; const OBSERVER_CONFIG = { subtree: true, attributes: true, @@ -18,18 +19,18 @@ const OBSERVER_CONFIG = { characterData: true, characterDataOldValue: false }; -const logInfo = createLogInfo(LOG_PRE_FIX); -const logError = createLogError(LOG_PRE_FIX); +const _logInfo = createLogInfo(LOG_PRE_FIX); +const _logError = createLogError(LOG_PRE_FIX); function createLogInfo(prefix) { return function (...strings) { - utils.logInfo(prefix + ' ', ...strings); + logInfo(prefix + ' ', ...strings); } } function createLogError(prefix) { return function (...strings) { - utils.logError(prefix + ' ', ...strings); + logError(prefix + ' ', ...strings); } } @@ -38,39 +39,39 @@ function getEmail(value) { if (!matched) { return null; } - logInfo('Email found: ' + matched[0]); + _logInfo('Email found: ' + matched[0]); return matched[0]; } function bodyAction(mutations, observer) { - logInfo('BODY observer on debounce called'); + _logInfo('BODY observer on debounce called'); // If the email is found in the input element, disconnect the observer if (email) { observer.disconnect(); - logInfo('Email is found, body observer disconnected'); + _logInfo('Email is found, body observer disconnected'); return; } const body = document.body.innerHTML; email = getEmail(body); if (email !== null) { - logInfo(`Email obtained from the body ${email}`); + _logInfo(`Email obtained from the body ${email}`); observer.disconnect(); - logInfo('Post data on email found in body'); + _logInfo('Post data on email found in body'); postData(); } } function targetAction(mutations, observer) { - logInfo('Target observer called'); + _logInfo('Target observer called'); for (const mutation of mutations) { for (const node of mutation.addedNodes) { email = node.textContent; if (email) { - logInfo('Email obtained from the target ' + email); + _logInfo('Email obtained from the target ' + email); observer.disconnect(); - logInfo('Post data on email found in target'); + _logInfo('Post data on email found in target'); postData(); return; } @@ -78,19 +79,38 @@ function targetAction(mutations, observer) { } } -function addInputElementsElementListner(conf) { - logInfo('Adding input element listeners'); +function addInputElementsElementListner() { + if (doesInputElementsHaveEmail()) { + _logInfo('Email found in input elements ' + email); + _logInfo('Post data on email found in target without'); + postData(); + return; + } + _logInfo('Adding input element listeners'); const inputs = document.querySelectorAll('input[type=text], input[type=email]'); for (var i = 0; i < inputs.length; i++) { - logInfo(`Original Value in Input = ${inputs[i].value}`); + _logInfo(`Original Value in Input = ${inputs[i].value}`); inputs[i].addEventListener('change', event => processInputChange(event)); inputs[i].addEventListener('blur', event => processInputChange(event)); } } +function addFormInputElementsElementListner(id) { + _logInfo('Adding input element listeners'); + if (doesFormInputElementsHaveEmail(id)) { + _logInfo('Email found in input elements ' + email); + postData(); + return; + } + _logInfo('Adding input element listeners'); + const input = document.getElementById(id); + input.addEventListener('change', event => processInputChange(event)); + input.addEventListener('blur', event => processInputChange(event)); +} + function removeInputElementsElementListner() { - logInfo('Removing input element listeners'); + _logInfo('Removing input element listeners'); const inputs = document.querySelectorAll('input[type=text], input[type=email]'); for (var i = 0; i < inputs.length; i++) { @@ -101,10 +121,10 @@ function removeInputElementsElementListner() { function processInputChange(event) { const value = event.target.value; - logInfo(`Modified Value of input ${event.target.value}`); + _logInfo(`Modified Value of input ${event.target.value}`); email = getEmail(value); if (email !== null) { - logInfo('Email found in input ' + email); + _logInfo('Email found in input ' + email); postData(); removeInputElementsElementListner(); } @@ -124,7 +144,7 @@ function debounce(func, wait, immediate) { if (callNow) { func.apply(context, args); } else { - logInfo('Debounce wait time ' + wait); + _logInfo('Debounce wait time ' + wait); timeout = setTimeout(later, wait); } }; @@ -138,31 +158,25 @@ function handleTargetElement() { email = targetElement.innerText; if (!email) { - logInfo('Finding the email with observer'); + _logInfo('Finding the email with observer'); targetObserver.observe(targetElement, OBSERVER_CONFIG); } else { - logInfo('Target found with target ' + email); - logInfo('Post data on email found in target with target'); + _logInfo('Target found with target ' + email); + _logInfo('Post data on email found in target with target'); postData(); } } } function handleBodyElements() { - if (doesInputElementsHaveEmail()) { - logInfo('Email found in input elements ' + email); - logInfo('Post data on email found in target without'); - postData(); - return; - } email = getEmail(document.body.innerHTML); if (email !== null) { - logInfo('Email found in body ' + email); - logInfo('Post data on email found in the body without observer'); + _logInfo('Email found in body ' + email); + _logInfo('Post data on email found in the body without observer'); postData(); return; } - addInputElementsElementListner(); + if (conf.fullscan === true) { const bodyObserver = new MutationObserver(debounce(bodyAction, conf.debounce, false)); bodyObserver.observe(document.body, OBSERVER_CONFIG); @@ -182,13 +196,24 @@ function doesInputElementsHaveEmail() { return false; } +function doesFormInputElementsHaveEmail(formElementId) { + const input = document.getElementById(formElementId); + if (input) { + email = getEmail(input.value); + if (email !== null) { + return true; + } + } + return false; +} + function syncCallback() { return { success: function () { - logInfo('Data synced successfully.'); + _logInfo('Data synced successfully.'); }, error: function () { - logInfo('Data sync failed.'); + _logInfo('Data sync failed.'); } } } @@ -197,15 +222,15 @@ function postData() { (getGlobal()).refreshUserIds(); const userIds = (getGlobal()).getUserIds(); if (Object.keys(userIds).length === 0) { - logInfo('No user ids'); + _logInfo('No user ids'); return; } - logInfo('Users' + userIds); + _logInfo('Users' + userIds); const syncPayload = {}; syncPayload.hid = MD5(email).toString(); syncPayload.uids = userIds; const payloadString = JSON.stringify(syncPayload); - logInfo(payloadString); + _logInfo(payloadString); ajax(conf.url, syncCallback(), payloadString, {method: 'POST', withCredentials: true}); } @@ -213,6 +238,10 @@ function associateIds() { if (window.MutationObserver || window.WebKitMutationObserver) { if (conf.target) { handleTargetElement(); + } else if (conf.formElementId) { + addFormInputElementsElementListner(conf.formElementId); + } else if (conf.inputscan) { + addInputElementsElementListner(); } else { handleBodyElements(); } @@ -221,20 +250,28 @@ function associateIds() { export function setConfig(config) { if (!config) { - logError('Required confirguration not provided'); + _logError('Required confirguration not provided'); return; } if (!config.url) { - logError('The required url is not configured'); + _logError('The required url is not configured'); return; } if (typeof config.debounce !== 'number') { config.debounce = CONF_DEFAULT_OBSERVER_DEBOUNCE_MS; - logInfo('Set default observer debounce to ' + CONF_DEFAULT_OBSERVER_DEBOUNCE_MS); + _logInfo('Set default observer debounce to ' + CONF_DEFAULT_OBSERVER_DEBOUNCE_MS); } if (typeof config.fullscan !== 'boolean') { config.fullscan = CONF_DEFAULT_FULL_BODY_SCAN; - logInfo('Set default fullscan ' + CONF_DEFAULT_FULL_BODY_SCAN); + _logInfo('Set default fullscan ' + CONF_DEFAULT_FULL_BODY_SCAN); + } + if (typeof config.inputscan !== 'boolean') { + config.inputscan = CONF_DEFAULT_INPUT_SCAN; + _logInfo('Set default input scan ' + CONF_DEFAULT_INPUT_SCAN); + } + + if (typeof config.formElementId == 'string') { + _logInfo('Looking for formElementId ' + config.formElementId); } conf = config; associateIds(); diff --git a/modules/idImportLibrary.md b/modules/idImportLibrary.md index 3dd78ee25d8..f91ca984bb3 100644 --- a/modules/idImportLibrary.md +++ b/modules/idImportLibrary.md @@ -8,6 +8,8 @@ | `url` | Yes | String | N/A | URL endpoint used to post the hashed email and user IDs. | | `debounce` | No | Number | 250 | Time in milliseconds before the email and IDs are fetched. | | `fullscan` | No | Boolean | false | Enable/disable a full page body scan to get email. | +| `formElementId` | No | String | N/A | ID attribute of the input (type=text/email) from which the email can be read. | +| `inputscan` | No | Boolean | N/A | Enable/disable a input element (type=text/email) scan to get email. | ## Example @@ -18,5 +20,7 @@ pbjs.setConfig({ url: 'https://example.com', debounce: 250, fullscan: false, + inputscan: false, + formElementId: "userid" }, }); diff --git a/modules/idxIdSystem.js b/modules/idxIdSystem.js index 00e8a8bc5e5..908edad4c04 100644 --- a/modules/idxIdSystem.js +++ b/modules/idxIdSystem.js @@ -4,7 +4,7 @@ * @module modules/idxIdSystem * @requires module:modules/userId */ -import * as utils from '../src/utils.js' +import { isStr, isPlainObject, logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -34,7 +34,7 @@ export const idxIdSubmodule = { * @return { Object | string | undefined } */ decode(value) { - const idxVal = value ? utils.isStr(value) ? value : utils.isPlainObject(value) ? value.id : undefined : undefined; + const idxVal = value ? isStr(value) ? value : isPlainObject(value) ? value.id : undefined : undefined; return idxVal ? { 'idx': idxVal } : undefined; @@ -52,7 +52,7 @@ export const idxIdSubmodule = { const idxObj = JSON.parse(idxString); return idxObj && idxObj.idx ? { id: idxObj.idx } : undefined; } catch (error) { - utils.logError(error); + logError(error); } } return undefined; diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js new file mode 100644 index 00000000000..6c582df3df3 --- /dev/null +++ b/modules/imRtdProvider.js @@ -0,0 +1,238 @@ +/** + * The {@link module:modules/realTimeData} module is required + * The module will fetch real-time data from Intimate Merger + * @module modules/imRtdProvider + * @requires module:modules/realTimeData + */ +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js' +import {getStorageManager} from '../src/storageManager.js'; +import { + deepSetValue, + deepAccess, + timestamp, + mergeDeep, + logError, + logInfo, + isFn +} from '../src/utils.js' +import {submodule} from '../src/hook.js'; + +export const imUidLocalName = '__im_uid'; +export const imVidCookieName = '_im_vid'; +export const imRtdLocalName = '__im_sids'; +export const storage = getStorageManager(); +const submoduleName = 'im'; +const segmentsMaxAge = 3600000; // 1 hour (30 * 60 * 1000) +const uidMaxAge = 1800000; // 30 minites (30 * 60 * 1000) +const vidMaxAge = 97200000000; // 37 months ((365 * 3 + 30) * 24 * 60 * 60 * 1000) + +function setImDataInCookie(value) { + storage.setCookie( + imVidCookieName, + value, + new Date(timestamp() + vidMaxAge).toUTCString(), + 'none' + ); +} + +/** +* @param {string} bidderName +*/ +export function getBidderFunction(bidderName) { + const biddersFunction = { + ix: function (bid, data) { + if (data.im_segments && data.im_segments.length) { + config.setConfig({ + ix: {firstPartyData: {im_segments: data.im_segments}}, + }); + } + return bid + }, + pubmatic: function (bid, data) { + if (data.im_segments && data.im_segments.length) { + const dctr = deepAccess(bid, 'params.dctr'); + deepSetValue( + bid, + 'params.dctr', + `${dctr ? dctr + '|' : ''}im_segments=${data.im_segments.join(',')}` + ); + } + return bid + }, + fluct: function (bid, data) { + if (data.im_segments && data.im_segments.length) { + deepSetValue( + bid, + 'params.kv.imsids', + data.im_segments + ); + } + return bid + } + } + return biddersFunction[bidderName] || null; +} + +export function getCustomBidderFunction(config, bidder) { + const overwriteFn = deepAccess(config, `params.overwrites.${bidder}`) + + if (overwriteFn && isFn(overwriteFn)) { + return overwriteFn + } else { + return null + } +} + +/** + * Add real-time data. + * @param {Object} bidConfig + * @param {Object} moduleConfig + * @param {Object} data + */ +export function setRealTimeData(bidConfig, moduleConfig, data) { + const adUnits = bidConfig.adUnits || getGlobal().adUnits; + const utils = {deepSetValue, deepAccess, logInfo, logError, mergeDeep}; + + if (data.im_segments) { + const ortb2 = config.getConfig('ortb2') || {}; + deepSetValue(ortb2, 'user.ext.data.im_segments', data.im_segments); + config.setConfig({ortb2: ortb2}); + + if (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues')) { + window.googletag = window.googletag || {cmd: []}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(() => { + window.googletag.pubads().setTargeting('im_segments', data.im_segments); + }); + } + } + + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const bidderFunction = getBidderFunction(bid.bidder); + const overwriteFunction = getCustomBidderFunction(moduleConfig, bid.bidder); + if (overwriteFunction) { + overwriteFunction(bid, data, utils, config); + } else if (bidderFunction) { + bidderFunction(bid, data); + } + }) + }); +} + +/** + * Real-time data retrieval from Intimate Merger + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} moduleConfig + */ +export function getRealTimeData(reqBidsConfigObj, onDone, moduleConfig) { + const cid = deepAccess(moduleConfig, 'params.cid'); + if (!cid) { + logError('imRtdProvider requires a valid cid to be defined'); + onDone(); + return; + } + const sids = storage.getDataFromLocalStorage(imRtdLocalName); + const parsedSids = sids ? sids.split(',') : []; + const mt = storage.getDataFromLocalStorage(`${imRtdLocalName}_mt`); + const localVid = storage.getCookie(imVidCookieName); + let apiUrl = `https://sync6.im-apps.net/${cid}/rtd`; + let expired = true; + let alreadyDone = false; + + if (localVid) { + apiUrl += `?vid=${localVid}`; + setImDataInCookie(localVid); + } + + if (Date.parse(mt) && Date.now() - (new Date(mt)).getTime() < segmentsMaxAge) { + expired = false; + } + + if (sids !== null) { + setRealTimeData(reqBidsConfigObj, moduleConfig, {im_segments: parsedSids}); + onDone(); + alreadyDone = true; + } + + if (expired) { + ajax( + apiUrl, + getApiCallback(reqBidsConfigObj, alreadyDone ? undefined : onDone, moduleConfig), + undefined, + {method: 'GET', withCredentials: true} + ); + } +} + +/** + * Api callback from Intimate Merger + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} moduleConfig + */ +export function getApiCallback(reqBidsConfigObj, onDone, moduleConfig) { + return { + success: function (response, req) { + let parsedResponse = {}; + if (req.status === 200) { + try { + parsedResponse = JSON.parse(response); + } catch (e) { + logError('unable to get Intimate Merger segment data'); + } + + if (parsedResponse.uid) { + const imuid = storage.getDataFromLocalStorage(imUidLocalName); + const imuidMt = storage.getDataFromLocalStorage(`${imUidLocalName}_mt`); + const imuidExpired = Date.parse(imuidMt) && Date.now() - (new Date(imuidMt)).getTime() < uidMaxAge; + if (!imuid || imuidExpired) { + storage.setDataInLocalStorage(imUidLocalName, parsedResponse.uid); + storage.setDataInLocalStorage(`${imUidLocalName}_mt`, new Date(timestamp()).toUTCString()); + } + } + + if (parsedResponse.vid) { + setImDataInCookie(parsedResponse.vid); + } + + if (parsedResponse.segments) { + setRealTimeData(reqBidsConfigObj, moduleConfig, {im_segments: parsedResponse.segments}); + storage.setDataInLocalStorage(imRtdLocalName, parsedResponse.segments); + storage.setDataInLocalStorage(`${imRtdLocalName}_mt`, new Date(timestamp()).toUTCString()); + } + } + if (onDone) { + onDone(); + } + }, + error: function () { + if (onDone) { + onDone(); + } + logError('unable to get Intimate Merger segment data'); + } + } +} + +/** + * Module init + * @param {Object} provider + * @param {Object} userConsent + * @return {boolean} + */ +function init(provider, userConsent) { + return true; +} + +/** @type {RtdSubmodule} */ +export const imRtdSubmodule = { + name: submoduleName, + getBidRequestData: getRealTimeData, + init: init +}; + +submodule('realTimeData', imRtdSubmodule); diff --git a/modules/imRtdProvider.md b/modules/imRtdProvider.md new file mode 100644 index 00000000000..7ece2b996b4 --- /dev/null +++ b/modules/imRtdProvider.md @@ -0,0 +1,41 @@ +## Intimate Merger Real-time Data Submodule + +provided by Intimate Merger. + +## Building Prebid with Real-time Data Support + +First, make sure to add the Intimate Merger submodule to your Prebid.js package with: + +`gulp build --modules=rtdModule,imRtdProvider` + +The following configuration parameters are available: + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 5000, + dataProviders: [ + { + name: "im", + waitForIt: true, + params: { + cid: 5126, // Set your Intimate Merger Customer ID here for production + setGptKeyValues: true + } + } + ] + } + ... +} +``` + +### Parameter Descriptions for the im Configuration Section + +| Param under dataProviders | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `"im"` | +| waitForIt | Optional | Boolean | Required to ensure that the auction is delayed until prefetch is complete. Defaults to false but recommended to true | `true` | +| params | Required | Object | Details of module params. | | +| params.cid | Required | Number | This is the Customer ID value obtained via Intimate Merger. | `5126` | +| params.setGptKeyValues | Optional | Boolean | This is set targeting for GPT/GAM. Default setting is true. | `true` | diff --git a/modules/imonomyBidAdapter.js b/modules/imonomyBidAdapter.js deleted file mode 100644 index b9205943f65..00000000000 --- a/modules/imonomyBidAdapter.js +++ /dev/null @@ -1,130 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'imonomy'; -const ENDPOINT = 'https://b.imonomy.com/openrtb/hb/00000'; -const USYNCURL = 'https://b.imonomy.com/UserMatching/b/'; - -export const spec = { - code: BIDDER_CODE, - - /** - * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid - * - * @param {BidRequest} bid The bid params to validate. - * @return {boolean} True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: bid => { - return !!(bid && bid.params && bid.params.placementId && bid.params.hbid); - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: validBidRequests => { - const tags = validBidRequests.map(bid => { - // map each bid id to bid object to retrieve adUnit code in callback - let tag = { - uuid: bid.bidId, - sizes: bid.sizes, - trid: bid.transactionId, - hbid: bid.params.hbid, - placementid: bid.params.placementId - }; - - // add floor price if specified (not mandatory) - if (bid.params.floorPrice) { - tag.floorprice = bid.params.floorPrice; - } - - return tag; - }); - - // Imonomy server config - const time = new Date().getTime(); - const kbConf = { - ts_as: time, - hb_placements: [], - hb_placement_bidids: {}, - hb_floors: {}, - cb: _generateCb(time), - tz: new Date().getTimezoneOffset(), - }; - - validBidRequests.forEach(bid => { - kbConf.hdbdid = kbConf.hdbdid || bid.params.hbid; - kbConf.encode_bid = kbConf.encode_bid || bid.params.encode_bid; - kbConf.hb_placement_bidids[bid.params.placementId] = bid.bidId; - if (bid.params.floorPrice) { - kbConf.hb_floors[bid.params.placementId] = bid.params.floorPrice; - } - kbConf.hb_placements.push(bid.params.placementId); - }); - - let payload = {}; - if (!utils.isEmpty(tags)) { - payload = { bids: [...tags], kbConf }; - } - - let endpointToUse = ENDPOINT; - if (kbConf.hdbdid) { - endpointToUse = endpointToUse.replace('00000', kbConf.hdbdid); - } - - return { - method: 'POST', - url: endpointToUse, - data: JSON.stringify(payload) - }; - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} response A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: (response) => { - const bidResponses = []; - if (response && response.body && response.body.bids) { - response.body.bids.forEach(bid => { - // The bid ID. Used to tie this bid back to the request. - if (bid.uuid) { - bid.requestId = bid.uuid; - } else { - utils.logError('No uuid for bid'); - } - // The creative payload of the returned bid. - if (bid.creative) { - bid.ad = bid.creative; - } else { - utils.logError('No creative for bid'); - } - bidResponses.push(bid); - }); - } - return bidResponses; - }, - /** - * Register User Sync. - */ - getUserSyncs: syncOptions => { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: USYNCURL - }]; - } - } -}; - -/** -* Generated cache baster value to be sent to bid server -* @param {*} time current time to use for creating cb. -*/ -function _generateCb(time) { - return Math.floor((time % 65536) + (Math.floor(Math.random() * 65536) * 65536)); -} - -registerBidder(spec); diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index b649b5a8a73..b204e81f22c 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -1,13 +1,14 @@ +import { deepAccess, deepSetValue, generateUUID } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; import {ajax} from '../src/ajax.js'; +import { createEidsArray } from './userId/eids.js'; const BIDDER_CODE = 'impactify'; const BIDDER_ALIAS = ['imp']; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_VIDEO_WIDTH = 640; -const DEFAULT_VIDEO_HEIGHT = 480; +const DEFAULT_VIDEO_HEIGHT = 360; const ORIGIN = 'https://sonic.impactify.media'; const LOGGER_URI = 'https://logger.impactify.media'; const AUCTIONURI = '/bidder'; @@ -32,12 +33,24 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { id: bidderRequest.auctionId, validBidRequests, cur: [DEFAULT_CURRENCY], - imp: [] + imp: [], + source: {tid: bidderRequest.auctionId} }; // Force impactify debugging parameter - if (window.localStorage.getItem('_im_db_bidder') == 3) { - request.test = 3; + if (window.localStorage.getItem('_im_db_bidder') != null) { + request.test = Number(window.localStorage.getItem('_im_db_bidder')); + } + + // Set Schain in request + let schain = deepAccess(validBidRequests, '0.schain'); + if (schain) request.source.ext = { schain: schain }; + + // Set eids + let bidUserId = deepAccess(validBidRequests, '0.userId'); + let eids = createEidsArray(bidUserId); + if (eids.length) { + deepSetValue(request, 'user.ext.eids', eids); } // Set device/user/site @@ -55,26 +68,26 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { request.site = {page: bidderRequest.refererInfo.referer}; // Handle privacy settings for GDPR/CCPA/COPPA + let gdprApplies = 0; if (bidderRequest.gdprConsent) { - let gdprApplies = 0; if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); - utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } + deepSetValue(request, 'regs.ext.gdpr', gdprApplies); if (bidderRequest.uspConsent) { - utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); this.syncStore.uspConsent = bidderRequest.uspConsent; } - if (GETCONFIG('coppa') == true) utils.deepSetValue(request, 'regs.coppa', 1); + if (GETCONFIG('coppa') == true) deepSetValue(request, 'regs.coppa', 1); if (bidderRequest.uspConsent) { - utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); } // Set buyer uid - utils.deepSetValue(request, 'user.buyeruid', utils.generateUUID()); + deepSetValue(request, 'user.buyeruid', generateUUID()); // Create imps with bids validBidRequests.forEach((bid) => { @@ -106,8 +119,9 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: ['video'], + supportedMediaTypes: ['video', 'banner'], aliases: BIDDER_ALIAS, + /** * Determines whether or not the given bid request is valid. * @@ -127,6 +141,7 @@ export const spec = { return true; }, + /** * Make a server request from the list of BidRequests. * @@ -144,6 +159,7 @@ export const spec = { data: JSON.stringify(request), }; }, + /** * Unpack the response from the server into a list of bids. * @@ -180,7 +196,10 @@ export const spec = { ttl: 300, creativeId: bid.crid || 0, hash: bid.hash, - expiry: bid.expiry + expiry: bid.expiry, + meta: { + advertiserDomains: bid.adomain && bid.adomain.length ? bid.adomain : [] + } })), ]; } diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index dc0911ff5da..a68e90754fb 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { deepSetValue, logError, _each, getBidRequest, isNumber, isArray, deepAccess, isFn, isPlainObject, logWarn, getBidIdParameter, getUniqueIdentifierStr, isEmpty, isInteger, isStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -11,7 +11,7 @@ const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js const VIDEO_TARGETING = ['skip', 'skipmin', 'skipafter']; export const spec = { - version: '7.3.0', + version: '7.6.0', code: BIDDER_CODE, gvlid: 253, aliases: ['id'], @@ -45,8 +45,24 @@ export const spec = { libVersion: this.version }; - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) { - requestParameters.gdpr = bidderRequest.gdprConsent.consentString; + const gdprConsent = deepAccess(bidderRequest, 'gdprConsent') + if (gdprConsent) { + // GDPR Consent String + if (gdprConsent.consentString) { + requestParameters.gdpr = gdprConsent.consentString; + } + + // Additional Consent String + const additionalConsent = deepAccess(gdprConsent, 'addtlConsent'); + if (additionalConsent && additionalConsent.indexOf('~') !== -1) { + // Google Ad Tech Provider IDs + const atpIds = additionalConsent.substring(additionalConsent.indexOf('~') + 1); + deepSetValue( + requestParameters, + 'user.ext.consented_providers_settings.consented_providers', + atpIds.split('.').map(id => parseInt(id, 10)) + ); + } } if (bidderRequest && bidderRequest.uspConsent) { @@ -57,12 +73,29 @@ export const spec = { requestParameters.referrer = bidderRequest.refererInfo.referer; } + // Adding first party data + const site = config.getConfig('ortb2.site'); + if (site) { + const pageCategory = site.pagecat || site.cat; + if (pageCategory && isArray(pageCategory)) { + requestParameters.pagecat = pageCategory.filter((category) => { + return category && isStr(category) + }); + } + const genre = deepAccess(site, 'content.genre'); + if (genre && isStr(genre)) { + requestParameters.genre = genre; + } + } + // End of adding first party data + requestParameters.schain = bidRequests[0].schain; + requestParameters.coppa = config.getConfig('coppa') === true; if (bidRequests[0].userId) { const eids = createEidsArray(bidRequests[0].userId); if (eids.length) { - utils.deepSetValue(requestParameters, 'user.ext.eids', eids); + deepSetValue(requestParameters, 'user.ext.eids', eids); } } @@ -72,7 +105,7 @@ export const spec = { ); if (requestObj.errors && requestObj.errors.length > 0) { - utils.logError('ID WARNING 0x01'); + logError('ID WARNING 0x01'); } requestObj.requests.forEach(request => request.bidderRequest = bidderRequest); return requestObj.requests; @@ -86,13 +119,13 @@ export const spec = { */ interpretResponse: function (serverResponse, {bidderRequest}) { const bids = []; - utils._each(serverResponse.body.bid, function (bidObject) { + _each(serverResponse.body.bid, function (bidObject) { if (!bidObject.price || bidObject.price === null || bidObject.hasOwnProperty('errorCode') || (!bidObject.adm && !bidObject.native)) { return; } - const bidRequest = utils.getBidRequest(bidObject.id, [bidderRequest]); + const bidRequest = getBidRequest(bidObject.id, [bidderRequest]); const bid = {}; if (bidObject.native) { @@ -132,7 +165,7 @@ export const spec = { // Deal ID. Composite ads can have multiple line items and the ID of the first // dealID line item will be used. - if (utils.isNumber(bidObject.lid) && bidObject.buying_type && bidObject.buying_type !== 'rtb') { + 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) && @@ -158,6 +191,12 @@ export const spec = { bid.height = 1; } + if (bidObject.adomain) { + bid.meta = { + advertiserDomains: bidObject.adomain + }; + } + bids.push(bid); }); return bids; @@ -175,7 +214,7 @@ export const spec = { const syncs = []; serverResponses.forEach(response => { response.body.bid.forEach(bidObject => { - if (utils.isArray(bidObject.sync)) { + if (isArray(bidObject.sync)) { bidObject.sync.forEach(syncElement => { if (syncs.indexOf(syncElement) === -1) { syncs.push(syncElement); @@ -191,15 +230,15 @@ export const spec = { }; function isInstreamVideo(bid) { - const mediaTypes = Object.keys(utils.deepAccess(bid, 'mediaTypes', {})); - const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + 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 = utils.deepAccess(bid, 'mediaTypes.video'); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + const videoMediaType = deepAccess(bid, 'mediaTypes.video'); + const context = deepAccess(bid, 'mediaTypes.video.context'); return videoMediaType && context === 'outstream'; } @@ -218,6 +257,21 @@ function getVideoTargetingParams(bid) { 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({ @@ -238,34 +292,32 @@ function createRenderer(bidRequest) { id: bidRequest.adUnitCode, url: RENDERER_URL, loaded: false, - config: utils.deepAccess(bidRequest, 'renderer.options'), + config: deepAccess(bidRequest, 'renderer.options'), adUnitCode: bidRequest.adUnitCode }); try { renderer.setRender(outstreamRender); } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); + logWarn('Prebid Error calling setRender on renderer', err); } return renderer; } function getNormalizedBidRequest(bid) { - let adUnitId = utils.getBidIdParameter('adUnitCode', bid) || null; - let placementId = utils.getBidIdParameter('placementId', bid.params) || null; + let adUnitId = getBidIdParameter('adUnitCode', bid) || null; + let placementId = getBidIdParameter('placementId', bid.params) || null; let publisherId = null; let placementKey = null; if (placementId === null) { - publisherId = utils.getBidIdParameter('publisherId', bid.params) || null; - placementKey = utils.getBidIdParameter('placementKey', bid.params) || null; + publisherId = getBidIdParameter('publisherId', bid.params) || null; + placementKey = getBidIdParameter('placementKey', bid.params) || null; } - const keyValues = utils.getBidIdParameter('keyValues', bid.params) || null; - const singleSizeFilter = utils.getBidIdParameter('size', bid.params) || null; - const bidId = utils.getBidIdParameter('bidId', bid); - const transactionId = utils.getBidIdParameter('transactionId', bid); + 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'); - const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); - const bidFloorCur = utils.getBidIdParameter('bidFloorCur', bid.params); let normalizedBidRequest = {}; if (isInstreamVideo(bid)) { @@ -309,6 +361,13 @@ function getNormalizedBidRequest(bid) { 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'; @@ -318,7 +377,7 @@ function getNormalizedBidRequest(bid) { function getNormalizedNativeAd(rawNative) { const native = {}; - if (!rawNative || !utils.isArray(rawNative.assets)) { + if (!rawNative || !isArray(rawNative.assets)) { return null; } // Assets @@ -449,7 +508,7 @@ export function ImproveDigitalAdServerJSClient(endPoint) { let impressionObjects = []; let impressionObject; - if (utils.isArray(requestObject)) { + if (isArray(requestObject)) { for (let counter = 0; counter < requestObject.length; counter++) { impressionObject = this.createImpressionObject(requestObject[counter]); impressionObjects.push(impressionObject); @@ -550,7 +609,7 @@ export function ImproveDigitalAdServerJSClient(endPoint) { if (requestParameters.requestId) { impressionBidRequestObject.id = requestParameters.requestId; } else { - impressionBidRequestObject.id = utils.getUniqueIdentifierStr(); + impressionBidRequestObject.id = getUniqueIdentifierStr(); } if (requestParameters.domain) { impressionBidRequestObject.domain = requestParameters.domain; @@ -579,6 +638,12 @@ export function ImproveDigitalAdServerJSClient(endPoint) { 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; } @@ -588,6 +653,10 @@ export function ImproveDigitalAdServerJSClient(endPoint) { } } + if (requestParameters.coppa) { + impressionBidRequestObject.coppa = 1; + } + return impressionBidRequestObject; }; @@ -599,7 +668,7 @@ export function ImproveDigitalAdServerJSClient(endPoint) { if (placementObject.id) { impressionObject.id = placementObject.id; } else { - impressionObject.id = utils.getUniqueIdentifierStr(); + impressionObject.id = getUniqueIdentifierStr(); } if (placementObject.adTypes) { impressionObject.ad_types = placementObject.adTypes; @@ -628,18 +697,18 @@ export function ImproveDigitalAdServerJSClient(endPoint) { if (placementObject.transactionId) { impressionObject.tid = placementObject.transactionId; } - if (!utils.isEmpty(placementObject.video)) { + 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) { - utils.logWarn(`video.skip: invalid value '${video.skip}'. Expected 0 or 1`); + logWarn(`video.skip: invalid value '${video.skip}'. Expected 0 or 1`); delete video.skip; } } - if (!utils.isEmpty(video)) { + if (!isEmpty(video)) { impressionObject.video = video; } } @@ -661,11 +730,11 @@ export function ImproveDigitalAdServerJSClient(endPoint) { // Set of desired creative sizes // Input Format: array of pairs, i.e. [[300, 250], [250, 250]] - if (placementObject.format && utils.isArray(placementObject.format)) { + if (placementObject.format && isArray(placementObject.format)) { const format = placementObject.format .filter(sizePair => sizePair.length === 2 && - utils.isInteger(sizePair[0]) && - utils.isInteger(sizePair[1]) && + isInteger(sizePair[0]) && + isInteger(sizePair[1]) && sizePair[0] >= 0 && sizePair[1] >= 0) .map(sizePair => { diff --git a/modules/imuIdSystem.js b/modules/imuIdSystem.js new file mode 100644 index 00000000000..72e81d243a3 --- /dev/null +++ b/modules/imuIdSystem.js @@ -0,0 +1,151 @@ +/** + * The {@link module:modules/userId} module is required + * @module modules/imuIdSystem + * + * @requires module:modules/userId + */ + +import { timestamp, logError } from '../src/utils.js'; +import { ajax } from '../src/ajax.js' +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +export const storage = getStorageManager(); + +export const storageKey = '__im_uid'; +export const cookieKey = '_im_vid'; +export const apiUrl = 'https://audiencedata.im-apps.net/imuid/get'; +const storageMaxAge = 1800000; // 30 minites (30 * 60 * 1000) +const cookiesMaxAge = 97200000000; // 37 months ((365 * 3 + 30) * 24 * 60 * 60 * 1000) + +export function setImDataInLocalStorage(value) { + storage.setDataInLocalStorage(storageKey, value); + storage.setDataInLocalStorage(`${storageKey}_mt`, new Date(timestamp()).toUTCString()); +} + +export function removeImDataFromLocalStorage() { + storage.removeDataFromLocalStorage(storageKey); + storage.removeDataFromLocalStorage(`${storageKey}_mt`); +} + +function setImDataInCookie(value) { + storage.setCookie( + cookieKey, + value, + new Date(timestamp() + cookiesMaxAge).toUTCString(), + 'none' + ); +} + +export function getLocalData() { + const mt = storage.getDataFromLocalStorage(`${storageKey}_mt`); + let expired = true; + if (Date.parse(mt) && Date.now() - (new Date(mt)).getTime() < storageMaxAge) { + expired = false; + } + return { + id: storage.getDataFromLocalStorage(storageKey), + vid: storage.getCookie(cookieKey), + expired: expired + }; +} + +export function apiSuccessProcess(jsonResponse) { + if (!jsonResponse) { + return; + } + if (jsonResponse.uid) { + setImDataInLocalStorage(jsonResponse.uid); + if (jsonResponse.vid) { + setImDataInCookie(jsonResponse.vid); + } + } else { + removeImDataFromLocalStorage(); + } +} + +export function getApiCallback(callback) { + return { + success: response => { + let responseObj = {}; + if (response) { + try { + responseObj = JSON.parse(response); + apiSuccessProcess(responseObj); + } catch (error) { + logError('User ID - imuid submodule: ' + error); + } + } + if (callback && responseObj.uid) { + callback(responseObj.uid); + } + }, + error: error => { + logError('User ID - imuid submodule was unable to get data from api: ' + error); + if (callback) { + callback(); + } + } + }; +} + +export function callImuidApi(apiUrl) { + return function (callback) { + ajax(apiUrl, getApiCallback(callback), undefined, {method: 'GET', withCredentials: true}); + }; +} + +export function getApiUrl(cid, url) { + if (url) { + return `${url}?cid=${cid}`; + } + return `${apiUrl}?cid=${cid}`; +} + +/** @type {Submodule} */ +export const imuIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'imuid', + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{imuid: string} | undefined} + */ + decode(id) { + if (id && typeof id === 'string') { + return {imuid: id}; + } + return undefined; + }, + /** + * @function + * @param {SubmoduleConfig} [config] + * @returns {{id: string} | undefined | {callback:function}}} + */ + getId(config) { + const configParams = (config && config.params) || {}; + if (!configParams || typeof configParams.cid !== 'number') { + logError('User ID - imuid submodule requires a valid cid to be defined'); + return undefined; + } + let apiUrl = getApiUrl(configParams.cid, configParams.url); + const localData = getLocalData(); + if (localData.vid) { + apiUrl += `&vid=${localData.vid}`; + setImDataInCookie(localData.vid); + } + + if (!localData.id) { + return {callback: callImuidApi(apiUrl)} + } + if (localData.expired) { + callImuidApi(apiUrl)(); + } + return {id: localData.id}; + } +}; + +submodule('userId', imuIdSubmodule); diff --git a/modules/imuIdSystem.md b/modules/imuIdSystem.md new file mode 100644 index 00000000000..81aa87ba1d4 --- /dev/null +++ b/modules/imuIdSystem.md @@ -0,0 +1,34 @@ +## Intimate Merger User ID Submodule + +IM-UID is a universal identifier provided by Intimate Merger. +The integration of [IM-UID](https://intimatemerger.com/r/uid) into Prebid.js consists of this module. + +## Building Prebid with IM-UID Support + +First, make sure to add the Intimate Merger submodule to your Prebid.js package with: + +``` +gulp build --modules=imuIdSystem,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'imuid', + params: { + cid: 5126 // Set your Intimate Merger Customer ID here for production + } + }] + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `"imuid"` | +| params | Required | Object | Details of module params. | | +| params.cid | Required | Number | This is the Customer ID value obtained via Intimate Merger. | `5126` | +| params.url | Optional | String | Use this to change the default endpoint URL. | `"https://example.com/some/api"` | diff --git a/modules/inmarBidAdapter.js b/modules/inmarBidAdapter.js index e1edd935587..0e056551b35 100755 --- a/modules/inmarBidAdapter.js +++ b/modules/inmarBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { logError } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -83,7 +83,7 @@ export const spec = { bidResponses.push(bidResponse); } } catch (error) { - utils.logError('Error while parsing inmar response', error); + logError('Error while parsing inmar response', error); } return bidResponses; }, diff --git a/modules/innityBidAdapter.js b/modules/innityBidAdapter.js index aae79818daf..0a2f701ef64 100644 --- a/modules/innityBidAdapter.js +++ b/modules/innityBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { parseSizesInput, timestamp } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'innity'; @@ -10,24 +10,20 @@ export const spec = { return !!(bid.params && bid.params.pub && bid.params.zone); }, buildRequests: function(validBidRequests, bidderRequest) { - let refererInfo = ''; - if (bidderRequest && bidderRequest.refererInfo) { - refererInfo = bidderRequest.refererInfo.referer || ''; - } return validBidRequests.map(bidRequest => { - let parseSized = utils.parseSizesInput(bidRequest.sizes); + let parseSized = parseSizesInput(bidRequest.sizes); let arrSize = parseSized[0].split('x'); return { method: 'GET', url: ENDPOINT, data: { - cb: utils.timestamp(), + cb: timestamp(), ver: 2, hb: 1, output: 'js', pub: bidRequest.params.pub, zone: bidRequest.params.zone, - url: encodeURIComponent(refererInfo), + url: bidderRequest && bidderRequest.refererInfo ? encodeURIComponent(bidderRequest.refererInfo.referer) : '', width: arrSize[0], height: arrSize[1], vpw: window.screen.width, @@ -51,6 +47,10 @@ export const spec = { netRevenue: true, ttl: 60, ad: '' + res.tag, + meta: { + advertiserDomains: res.adomain && res.adomain.length ? res.adomain : [], + mediaType: res.mediaType, + } }; return [bidResponse]; } diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js index 29040205818..781bca90660 100644 --- a/modules/inskinBidAdapter.js +++ b/modules/inskinBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { createTrackPixelHtml } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'inskin'; @@ -55,6 +55,22 @@ export const spec = { parallel: true }, validBidRequests[0].params); + if (validBidRequests[0].schain) { + data.rtb = { + schain: validBidRequests[0].schain + }; + } else if (data.publisherId) { + data.rtb = { + schain: { + ext: { + sid: String(data.publisherId) + } + } + }; + } + + delete data.publisherId; + data.keywords = data.keywords || []; const restrictions = []; @@ -169,6 +185,7 @@ export const spec = { bid.currency = 'USD'; bid.creativeId = decision.adId; bid.ttl = 360; + bid.meta = { advertiserDomains: decision.adomain ? decision.adomain : [] } bid.netRevenue = true; bidResponses.push(bid); @@ -276,7 +293,7 @@ function getSize(sizes) { } function retrieveAd(bidId, decision) { - return "`; + break; + } + }); + } + + if (nativeAdm.privacy) { + native.privacyLink = nativeAdm.privacy; + } + + return native; +} + +export const spec = { + code: BIDDER_CODE, + + gvlid: GVLID, + + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + + isBidRequestValid: function(bid) { + return !!(bid && !isEmpty(bid)); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + const payload = createOrtbTemplate(); + + // Pass the auctionId as ortb2 id + // See https://github.com/prebid/Prebid.js/issues/6563 + deepSetValue(payload, 'id', bidderRequest.auctionId); + deepSetValue(payload, 'source.tid', bidderRequest.auctionId); + + validBidRequests.forEach(validBid => { + let bid = deepClone(validBid); + + // No additional params atm. + const imp = createImp(bid); + + payload.imp.push(imp); + }); + + if (validBidRequests[0].schain) { + deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + if (bidderRequest && bidderRequest.uspConsent) { + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (config.getConfig('coppa') === true) { + deepSetValue(payload, 'regs.coppa', 1); + } + + if (deepAccess(validBidRequests[0], 'userId')) { + deepSetValue(payload, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); + } + + // Assign payload.site from refererinfo + if (bidderRequest.refererInfo) { + if (bidderRequest.refererInfo.reachedTop) { + const sitePage = bidderRequest.refererInfo.referer; + deepSetValue(payload, 'site.page', sitePage); + deepSetValue(payload, 'site.domain', parseUrl(sitePage, { + noDecodeWholeURL: true + }).hostname); + + if (canAccessTopWindow()) { + deepSetValue(payload, 'site.ref', getWindowTop().document.referrer); + } + } + } + + // Handle First Party Data (need publisher fpd setup) + const fpd = config.getConfig('ortb2') || {}; + if (fpd.site) { + mergeDeep(payload, { site: fpd.site }); + } + if (fpd.user) { + mergeDeep(payload, { user: fpd.user }); + } + + const request = { + method: 'POST', + url: ENDPOINT, + data: payload, + options: { + withCredentials: false + } + } + + return request; + }, + + interpretResponse(serverResponse, bidRequest) { + const bidResponses = []; + + try { + if (serverResponse.body && serverResponse.body.seatbid && isArray(serverResponse.body.seatbid)) { + const currency = serverResponse.body.cur || DEFAULT_CURRENCY; + const referrer = bidRequest.site && bidRequest.site.ref ? bidRequest.site.ref : ''; + + serverResponse.body.seatbid.forEach(bidderSeat => { + if (!isArray(bidderSeat.bid) || !bidderSeat.bid.length) { + return; + } + + bidderSeat.bid.forEach(bid => { + let mediaType; + // Actually only BANNER is supported, but other types will be added soon. + switch (deepAccess(bid, 'ext.prebid.type')) { + case 'V': + mediaType = VIDEO; + break; + case 'N': + mediaType = NATIVE; + break; + default: + mediaType = BANNER; + } + + const meta = { + advertiserDomains: (Array.isArray(bid.adomain) && bid.adomain.length) ? bid.adomain : [], + advertiserName: deepAccess(bid, 'ext.advertiser_name', null), + agencyName: deepAccess(bid, 'ext.agency_name', null), + primaryCatId: getPrimaryCatFromResponse(bid.cat), + mediaType + } + + const newBid = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0), + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + dealId: bid.dealid || null, + currency, + netRevenue: NET_REVENUE, + ttl: 360, // seconds. https://docs.prebid.org/dev-docs/faq.html#does-prebidjs-cache-bids + referrer, + ad: bid.adm, + mediaType, + burl: bid.burl, + meta: cleanObj(meta) + }; + + if (mediaType === NATIVE) { + const native = nativeBidResponseHandler(bid); + if (native) { + newBid.native = native; + } + } + + if (mediaType === VIDEO) { + // Note: + // Mediakeys bid adapter expects a publisher has set his own video player + // in the `mediaTypes.video` configuration object. + + // Mediakeys bidder does not provide inline XML in the bid response + // newBid.vastXml = bid.ext.vast_url; + + // For instream video, disable server cache as vast is generated per bid request + newBid.videoCacheKey = 'no_cache'; + + // The vast URL is server independently and must be fetched before video rendering in the renderer + // appending '&no_cache' is safe and fast as the vast url always have parameters + newBid.vastUrl = bid.ext.vast_url + '&no_cache'; + } + + bidResponses.push(newBid); + }); + }); + } + } catch (e) { + logError(BIDDER_CODE, e); + } + + return bidResponses; + }, + + onBidWon: function (bid) { + if (!bid.burl) { + return; + } + + const url = bid.burl.replace(/\$\{AUCTION_PRICE\}/, bid.cpm); + + triggerPixel(url); + } +} + +registerBidder(spec) diff --git a/modules/mediakeysBidAdapter.md b/modules/mediakeysBidAdapter.md new file mode 100644 index 00000000000..ec313c2fe3a --- /dev/null +++ b/modules/mediakeysBidAdapter.md @@ -0,0 +1,139 @@ +# Overview + +``` +Module Name: Mediakeys Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebidjs@mediakeys.com +``` + +# Description + +Connects to Mediakeys demand source to fetch bids. + +# Test Parameters + +## Banner only Ad Unit + +``` +var adUnits = [ +{ + code: 'test', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bids: [{ + bidder: 'mediakeys', + params: {} // no params required. + }] +}, +``` + +## Native only Ad Unit + +The Mediakeys adapter accepts two optional params for native requests. Please see the [OpenRTB Native Ads Specification](https://www.iab.com/wp-content/uploads/2018/03/OpenRTB-Native-Ads-Specification-Final-1.2.pdf) for valid values. + +``` +var adUnits = [ +{ + code: 'test', + mediaTypes: { + native: { + type: 'image', + } + }, + bids: [{ + bidder: 'mediakeys', + params: { + native: { + context: 1, // ORTB Native Context Type IDs. Default `1`. + plcmttype: 1, // ORTB Native Placement Type IDs. Default `1`. + } + } + }] +}, +``` + +## Video only Ad Unit + +The Mediakeys adapter accepts any valid openRTB 2.5 video property. Properties can be defined at the adUnit `mediaTypes.video` or `bid[].params` level. + +### Outstream context + +``` +var adUnits = [ +{ + code: 'test', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250], + // additional OpenRTB video params + // placement: 2, + // api: [1], + // … + } + }, + renderer: { + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: function (bid) { + var bidReqConfig = pbjs.adUnits.find(bidReq => bidReq.bidId === bid.impid); + + if (bidReqConfig && bidReqConfig.mediaTypes && bidReqConfig.mediaTypes.video && bidReqConfig.mediaTypes.video.context === 'outstream') { + var adResponse = fetch(bid.vastUrl).then(resp => resp.text()).then(text => ({ + ad: { + video: { + content: text, + player_width: bid.width || bidReqConfig.mediaTypes.video.playerSize[0], + player_height: bid.height || bidReqConfig.mediaTypes.video.playerSize[1], + } + } + })) + + adResponse.then((ad) => { + bid.renderer.push(() => { + ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: ad + }); + }); + }) + } + } + }, + bids: [{ + bidder: 'mediakeys', + params: { + video: { + // additional OpenRTB video params. Will be merged with params defined at mediaTypes level + } + } + }] +}, +``` + +### Instream context + +``` +var adUnits = [ +{ + code: 'test', + mediaTypes: { + video: { + context: 'instream', + playerSize: [300, 250], + // additional OpenRTB video params + // placement: 2, + // api: [1], + // … + } + }, + bids: [{ + bidder: 'mediakeys', + params: { + // additional OpenRTB video params. Will be merged with params defined at mediaTypes level + } + }] +}, +``` diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index cb65a6afc54..d484ce643a1 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -1,7 +1,7 @@ +import { triggerPixel, deepAccess, getWindowTop, uniques, groupBy, isEmpty, _map, isPlainObject, logInfo, logError } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import * as utils from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity } from '../src/auction.js'; @@ -42,17 +42,10 @@ const VALID_URL_KEY = ['canonical_url', 'og_url', 'twitter_url']; const DEFAULT_URL_KEY = 'page'; const LOG_TYPE = { - AP: 'AP', - PR: 'PR', APPR: 'APPR', RA: 'RA' }; -const BATCHING = { - SINGLE: 'SINGLE', - MULTI: 'MULTI' -} - let auctions = {}; let config; let pageDetails; @@ -66,7 +59,6 @@ class ErrorLogger { this.project = 'prebidanalytics'; this.dn = pageDetails.domain || ''; this.requrl = pageDetails.requrl || ''; - this.event = this.event; this.pbversion = PREBID_VERSION; this.cid = config.cid || ''; this.rd = additionalData; @@ -74,7 +66,7 @@ class ErrorLogger { send() { let url = EVENT_PIXEL_URL + '?' + formatQS(this); - utils.triggerPixel(url); + triggerPixel(url); } } @@ -89,18 +81,10 @@ class Configure { this.gdprConsent = undefined; this.gdprApplies = undefined; this.uspConsent = undefined; - this.pixelWaitTime = 0; - this.apLoggingPct = 0; - this.prLoggingPct = 0; - this.batching = BATCHING.SINGLE; this.shouldBeLogged = {}; this.mnetDebugConfig = ''; } - set publisherLper(plper) { - this.pubLper = plper; - } - getLoggingData() { return { cid: this.cid, @@ -139,24 +123,10 @@ class Configure { if (!isNaN(parseInt(response.percentage, 10))) { this.loggingPercent = response.percentage; } - - if (!isNaN(parseInt(response.pixelwaittime, 10))) { - this.pixelWaitTime = response.pixelwaittime; - } - - if (!isNaN(parseInt(response.aplper, 10))) { - this.apLoggingPct = response.aplper; - this.batching = BATCHING.MULTI; - } - - if (!isNaN(parseInt(response.prlper, 10))) { - this.prLoggingPct = response.prlper; - this.batching = BATCHING.MULTI; - } } overrideDomainLevelData(response) { - const domain = utils.deepAccess(response, 'domain.' + pageDetails.domain); + const domain = deepAccess(response, 'domain.' + pageDetails.domain); if (domain) { this.setDataFromResponse(domain); } @@ -179,14 +149,14 @@ class Configure { init() { // Forces Logging % to 100% let urlObj = URL.parseUrl(pageDetails.page); - if (utils.deepAccess(urlObj, 'search.medianet_test') || urlObj.hostname === 'localhost') { + if (deepAccess(urlObj, 'search.medianet_test') || urlObj.hostname === 'localhost') { this.loggingPercent = 100; this.ajaxState = CONFIG_PASS; this.debug = true; return; } - if (utils.deepAccess(urlObj, 'search.mnet_setconfig')) { - this.mnetDebugConfig = utils.deepAccess(urlObj, 'search.mnet_setconfig'); + if (deepAccess(urlObj, 'search.mnet_setconfig')) { + this.mnetDebugConfig = deepAccess(urlObj, 'search.mnet_setconfig'); } ajax( this._configURL(), @@ -231,7 +201,7 @@ class PageDetail { _getAttributeFromSelector(selector, attribute) { try { - let doc = utils.getWindowTop().document; + let doc = getWindowTop().document; let element = doc.querySelector(selector); if (element !== null && element[attribute]) { return element[attribute]; @@ -240,7 +210,7 @@ class PageDetail { } _getAbsoluteUrl(url) { - let aTag = utils.getWindowTop().document.createElement('a'); + let aTag = getWindowTop().document.createElement('a'); aTag.href = url; return aTag.href; @@ -268,18 +238,14 @@ class AdSlot { this.context = context; this.adext = adext; this.logged = {}; - this.logged[LOG_TYPE.PR] = false; - this.logged[LOG_TYPE.AP] = false; - this.logged[LOG_TYPE.APPR] = false; this.targeting = undefined; this.medianetPresent = 0; } getShouldBeLogged(logType) { if (!config.shouldBeLogged.hasOwnProperty(logType)) { - config.shouldBeLogged[logType] = isSampled(logType); + config.shouldBeLogged[logType] = isSampled(); } - config.shouldBeLogged[logType] = isSampled(logType); return config.shouldBeLogged[logType]; } @@ -343,7 +309,7 @@ class Bid { bdp: this.cpm, cbdp: this.dfpbd, dfpbd: this.dfpbd, - szs: this.allMediaTypeSizes.map(sz => sz.join('x')).join('|'), + szs: this.allMediaTypeSizes.join('|'), size: this.size, mtype: this.mediaType, dId: this.dealId, @@ -392,8 +358,8 @@ class Auction { flrdata: this._mergeFieldsToLog({ ln: this.floorData.location, skp: this.floorData.skipped, - enfj: utils.deepAccess(this.floorData, 'enforcements.enforceJS'), - enfd: utils.deepAccess(this.floorData, 'enforcements.floorDeals'), + enfj: deepAccess(this.floorData, 'enforcements.enforceJS'), + enfd: deepAccess(this.floorData, 'enforcements.floorDeals'), sr: this.floorData.skipRate, fs: this.floorData.fetchStatus }), @@ -445,7 +411,7 @@ function auctionInitHandler({auctionId, adUnits, timeout, timestamp, bidderReque auctions[auctionId].auctionInitTime = timestamp; } addAddSlots(auctionId, adUnits, timeout); - const floorData = utils.deepAccess(bidderRequests, '0.bids.0.floorData'); + const floorData = deepAccess(bidderRequests, '0.bids.0.floorData'); if (floorData) { auctions[auctionId].floorData = {...floorData}; } @@ -453,10 +419,10 @@ function auctionInitHandler({auctionId, adUnits, timeout, timestamp, bidderReque function addAddSlots(auctionId, adUnits, tmax) { adUnits = adUnits || []; - const groupedAdUnits = utils.groupBy(adUnits, 'code'); + const groupedAdUnits = groupBy(adUnits, 'code'); Object.keys(groupedAdUnits).forEach((adUnitCode) => { const adUnits = groupedAdUnits[adUnitCode]; - const supplyAdCode = utils.deepAccess(adUnits, '0.adUnitCode') || adUnitCode; + const supplyAdCode = deepAccess(adUnits, '0.adUnitCode') || adUnitCode; let context = ''; let adext = {}; @@ -464,21 +430,20 @@ function addAddSlots(auctionId, adUnits, tmax) { const oSizes = {banner: [], video: []}; adUnits.forEach(({mediaTypes, sizes, ext}) => { mediaTypes = mediaTypes || {}; - adext = Object.assign(adext, ext || utils.deepAccess(mediaTypes, 'banner.ext')); - context = utils.deepAccess(mediaTypes, 'video.context') || context; + adext = Object.assign(adext, ext || deepAccess(mediaTypes, 'banner.ext')); + context = deepAccess(mediaTypes, 'video.context') || context; Object.keys(mediaTypes).forEach((mediaType) => mediaTypeMap[mediaType] = 1); const sizeObject = _getSizes(mediaTypes, sizes); sizeObject.banner.forEach(size => oSizes.banner.push(size)); sizeObject.video.forEach(size => oSizes.video.push(size)); }); - adext = utils.isEmpty(adext) ? undefined : adext; - oSizes.banner = oSizes.banner.filter(utils.uniques); - oSizes.video = oSizes.video.filter(utils.uniques); - oSizes.native = mediaTypeMap.native === 1 ? [[1, 1]] : []; + adext = isEmpty(adext) ? undefined : adext; + oSizes.banner = oSizes.banner.filter(uniques); + oSizes.video = oSizes.video.filter(uniques); + oSizes.native = mediaTypeMap.native === 1 ? [[1, 1].join('x')] : []; const allMediaTypeSizes = [].concat(oSizes.banner, oSizes.native, oSizes.video); const mediaTypes = Object.keys(mediaTypeMap).join('|'); - auctions[auctionId].addSlot({adUnitCode, supplyAdCode, mediaTypes, allMediaTypeSizes, context, tmax, adext}); }); } @@ -503,22 +468,26 @@ function bidRequestedHandler({ auctionId, auctionStart, bids, start, uspConsent, const bidObj = new Bid(bidId, bidder, src, start, adUnitCode, mediaTypes && Object.keys(mediaTypes).join('|'), requestSizes); auctions[auctionId].addBid(bidObj); if (bidder === MEDIANET_BIDDER_CODE) { - bidObj.crid = utils.deepAccess(bid, 'params.crid'); - bidObj.pubcrid = utils.deepAccess(bid, 'params.crid'); + bidObj.crid = deepAccess(bid, 'params.crid'); + bidObj.pubcrid = deepAccess(bid, 'params.crid'); auctions[auctionId].adSlots[adUnitCode].medianetPresent = 1; } }); } function _getSizes(mediaTypes, sizes) { - const banner = utils.deepAccess(mediaTypes, 'banner.sizes') || sizes || []; - const native = utils.deepAccess(mediaTypes, 'native') ? [[1, 1]] : []; - const playerSize = utils.deepAccess(mediaTypes, 'video.playerSize') || []; + const banner = deepAccess(mediaTypes, 'banner.sizes') || sizes || []; + const native = deepAccess(mediaTypes, 'native') ? [[1, 1]] : []; + const playerSize = deepAccess(mediaTypes, 'video.playerSize') || []; let video = []; if (playerSize.length === 2) { video = [playerSize] } - return { banner, native, video } + return { + banner: banner.map(size => size.join('x')), + native: native.map(size => size.join('x')), + video: video.map(size => size.join('x')) + } } function bidResponseHandler(bid) { @@ -537,12 +506,12 @@ function bidResponseHandler(bid) { { cpm, width, height, mediaType, timeToRespond, dealId, creativeId }, { adId, currency } ); - bidObj.floorPrice = utils.deepAccess(bid, 'floorData.floorValue'); - bidObj.floorRule = utils.deepAccess(bid, 'floorData.floorRule'); + bidObj.floorPrice = deepAccess(bid, 'floorData.floorValue'); + bidObj.floorRule = deepAccess(bid, 'floorData.floorRule'); bidObj.originalCpm = originalCpm || cpm; - let dfpbd = utils.deepAccess(bid, 'adserverTargeting.hb_pb'); + let dfpbd = deepAccess(bid, 'adserverTargeting.hb_pb'); if (!dfpbd) { - let priceGranularity = getPriceGranularity(mediaType, bid); + let priceGranularity = getPriceGranularity(bid); let priceGranularityKey = PRICE_GRANULARITY[priceGranularity]; dfpbd = bid[priceGranularityKey] || cpm; } @@ -593,18 +562,12 @@ function bidTimeoutHandler(timedOutBids) { }) } -function auctionEndHandler({auctionId, auctionEnd, adUnitCodes}) { +function auctionEndHandler({auctionId, auctionEnd}) { if (!(auctions[auctionId] instanceof Auction)) { return; } auctions[auctionId].status = AUCTION_COMPLETED; auctions[auctionId].auctionEndTime = auctionEnd; - - if (config.batching === BATCHING.MULTI) { - adUnitCodes.forEach(function (adUnitCode) { - sendEvent(auctionId, adUnitCode, LOG_TYPE.PR); - }); - } } function setTargetingHandler(params) { @@ -635,34 +598,15 @@ function setTargetingHandler(params) { auctionObj.bids.forEach(bid => { if (bid.bidder === DUMMY_BIDDER && bid.adUnitCode === adunit) { bid.iwb = bidAdIds.length === 0 ? 0 : 1; - bid.width = utils.deepAccess(winningBid, 'width'); - bid.height = utils.deepAccess(winningBid, 'height'); + bid.width = deepAccess(winningBid, 'width'); + bid.height = deepAccess(winningBid, 'height'); } }); - sendEvent(auctionId, adunit, getLogType()); + sendEvent(auctionId, adunit, LOG_TYPE.APPR); } } } -function getLogType() { - if (config.batching === BATCHING.SINGLE) { - return LOG_TYPE.APPR; - } - return LOG_TYPE.AP; -} - -function setBidderDone(params) { - if (config.pixelWaitTime != null && config.pixelWaitTime > 0) { - setTimeout(fireApAfterWait, config.pixelWaitTime, params) - } -} - -function fireApAfterWait(params) { - params.bids.forEach(function (adUnit) { - sendEvent(params.auctionId, adUnit.adUnitCode, LOG_TYPE.AP); - }); -} - function bidWonHandler(bid) { const { requestId, auctionId, adUnitCode } = bid; if (!(auctions[auctionId] instanceof Auction)) { @@ -677,20 +621,8 @@ function bidWonHandler(bid) { sendEvent(auctionId, adUnitCode, LOG_TYPE.RA); } -function isSampled(logType) { - return Math.random() * 100 < parseFloat(getLogPercentage(logType)); -} - -function getLogPercentage(logType) { - let logPercentage = config.loggingPercent; - if (config.batching === BATCHING.MULTI) { - if (logType === LOG_TYPE.AP) { - logPercentage = config.apLoggingPct; - } else if (logType === LOG_TYPE.PR) { - logPercentage = config.prLoggingPct; - } - } - return logPercentage; +function isSampled() { + return Math.random() * 100 < parseFloat(config.loggingPercent); } function isValidAuctionAdSlot(acid, adtag) { @@ -711,13 +643,8 @@ function sendEvent(id, adunit, logType) { function fireApPrLog(auctionId, adUnitName, logType) { const adSlot = auctions[auctionId].adSlots[adUnitName]; if (adSlot.getShouldBeLogged(logType) && !adSlot.logged[logType]) { - if (config.batching === BATCHING.SINGLE) { - adSlot.logged[LOG_TYPE.AP] = true; - adSlot.logged[LOG_TYPE.PR] = true; - } else { - adSlot.logged[logType] = true; - } fireAuctionLog(auctionId, adUnitName, logType); + adSlot.logged[logType] = true; } } @@ -731,7 +658,7 @@ function getCommonLoggingData(acid, adtag) { function fireAuctionLog(acid, adtag, logType) { let commonParams = getCommonLoggingData(acid, adtag); commonParams.lgtp = logType; - let targeting = utils.deepAccess(commonParams, 'targ'); + let targeting = deepAccess(commonParams, 'targ'); Object.keys(commonParams).forEach((key) => (commonParams[key] == null) && delete commonParams[key]); delete commonParams.targ; @@ -759,11 +686,11 @@ function fireAuctionLog(acid, adtag, logType) { } function formatQS(data) { - return utils._map(data, (value, key) => { + return _map(data, (value, key) => { if (value === undefined) { return key + '='; } - if (utils.isPlainObject(value)) { + if (isPlainObject(value)) { value = JSON.stringify(value); } return key + '=' + encodeURIComponent(value); @@ -772,7 +699,7 @@ function formatQS(data) { function firePixel(qs) { logsQueue.push(ENDPOINT + '&' + qs); - utils.triggerPixel(ENDPOINT + '&' + qs); + triggerPixel(ENDPOINT + '&' + qs); } class URL { @@ -813,7 +740,7 @@ let medianetAnalytics = Object.assign(adapter({URL, analyticsType}), { }, track({ eventType, args }) { if (config.debug) { - utils.logInfo(eventType, args); + logInfo(eventType, args); } switch (eventType) { case CONSTANTS.EVENTS.AUCTION_INIT: { @@ -844,10 +771,6 @@ let medianetAnalytics = Object.assign(adapter({URL, analyticsType}), { setTargetingHandler(args); break; } - case CONSTANTS.EVENTS.BIDDER_DONE : { - setBidderDone(args); - break; - } case CONSTANTS.EVENTS.BID_WON: { bidWonHandler(args); break; @@ -859,7 +782,7 @@ medianetAnalytics.originEnableAnalytics = medianetAnalytics.enableAnalytics; medianetAnalytics.enableAnalytics = function (configuration) { if (!configuration || !configuration.options || !configuration.options.cid) { - utils.logError('Media.net Analytics adapter: cid is required.'); + logError('Media.net Analytics adapter: cid is required.'); return; } $$PREBID_GLOBAL$$.medianetGlobals = $$PREBID_GLOBAL$$.medianetGlobals || {}; @@ -868,7 +791,7 @@ medianetAnalytics.enableAnalytics = function (configuration) { pageDetails = new PageDetail(); config = new Configure(configuration.options.cid); - config.publisherLper = configuration.options.sampling || ''; + config.pubLper = configuration.options.sampling || ''; config.init(); configuration.options.sampling = 1; medianetAnalytics.originEnableAnalytics(configuration); diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index a2dc8bdfd03..db3921c9a47 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -1,5 +1,5 @@ +import { parseUrl, getWindowTop, isArray, getGptSlotInfoForAdUnitCode, isStr, deepAccess, isEmpty, logError, triggerPixel, buildUrl, isEmptyStr, logInfo } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { getRefererInfo } from '../src/refererDetection.js'; @@ -27,7 +27,7 @@ window.mnet = window.mnet || {}; window.mnet.queue = window.mnet.queue || []; mnData.urlData = { - domain: utils.parseUrl(refererInfo.referer).hostname, + domain: parseUrl(refererInfo.referer).hostname, page: refererInfo.referer, isTop: refererInfo.reachedTop } @@ -78,7 +78,7 @@ function getUrlFromSelector(selector, attribute) { function getAttributeFromSelector(selector, attribute) { try { - let doc = utils.getWindowTop().document; + let doc = getWindowTop().document; let element = doc.querySelector(selector); if (element !== null && element[attribute]) { return element[attribute]; @@ -87,7 +87,7 @@ function getAttributeFromSelector(selector, attribute) { } function getAbsoluteUrl(url) { - let aTag = utils.getWindowTop().document.createElement('a'); + let aTag = getWindowTop().document.createElement('a'); aTag.href = url; return aTag.href; @@ -98,7 +98,7 @@ function filterUrlsByType(urls, type) { } function transformSizes(sizes) { - if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) { + if (isArray(sizes) && sizes.length === 2 && !isArray(sizes[0])) { return [getSize(sizes)]; } @@ -114,8 +114,8 @@ function getSize(size) { function getWindowSize() { return { - w: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || -1, - h: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || -1 + w: window.screen.width || -1, + h: window.screen.height || -1 } } @@ -123,8 +123,8 @@ function getCoordinates(adUnitCode) { let element = document.getElementById(adUnitCode); if (!element && adUnitCode.indexOf('/') !== -1) { // now it means that adUnitCode is GAM AdUnitPath - const {divId} = utils.getGptSlotInfoForAdUnitCode(adUnitCode); - if (utils.isStr(divId)) { + const {divId} = getGptSlotInfoForAdUnitCode(adUnitCode); + if (isStr(divId)) { element = document.getElementById(divId); } } @@ -145,11 +145,11 @@ function getCoordinates(adUnitCode) { } function extParams(bidRequest, bidderRequests) { - const params = utils.deepAccess(bidRequest, 'params'); - const gdpr = utils.deepAccess(bidderRequests, 'gdprConsent'); - const uspConsent = utils.deepAccess(bidderRequests, 'uspConsent'); - const userId = utils.deepAccess(bidRequest, 'userId'); - const sChain = utils.deepAccess(bidRequest, 'schain') || {}; + const params = deepAccess(bidRequest, 'params'); + const gdpr = deepAccess(bidderRequests, 'gdprConsent'); + const uspConsent = deepAccess(bidderRequests, 'uspConsent'); + const userId = deepAccess(bidRequest, 'userId'); + const sChain = deepAccess(bidRequest, 'schain') || {}; const windowSize = spec.getWindowSize(); const gdprApplies = !!(gdpr && gdpr.gdprApplies); const uspApplies = !!(uspConsent); @@ -165,7 +165,7 @@ function extParams(bidRequest, bidderRequests) { windowSize.w !== -1 && windowSize.h !== -1 && { screen: windowSize }, userId && { user_id: userId }, $$PREBID_GLOBAL$$.medianetGlobals.analyticsEnabled && { analytics: true }, - !utils.isEmpty(sChain) && {schain: sChain} + !isEmpty(sChain) && {schain: sChain} ); } @@ -179,13 +179,18 @@ function slotParams(bidRequest) { }, all: bidRequest.params }; - let bannerSizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || []; - const videoInMediaType = utils.deepAccess(bidRequest, 'mediaTypes.video') || {}; - const videoInParams = utils.deepAccess(bidRequest, 'params.video') || {}; + if (bidRequest.ortb2Imp) { + params.ortb2Imp = bidRequest.ortb2Imp; + } + + let bannerSizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes') || []; + + const videoInMediaType = deepAccess(bidRequest, 'mediaTypes.video') || {}; + const videoInParams = deepAccess(bidRequest, 'params.video') || {}; const videoCombinedObj = Object.assign({}, videoInParams, videoInMediaType); - if (!utils.isEmpty(videoCombinedObj)) { + if (!isEmpty(videoCombinedObj)) { params.video = videoCombinedObj; } @@ -196,7 +201,7 @@ function slotParams(bidRequest) { try { params.native = JSON.stringify(bidRequest.nativeParams); } catch (e) { - utils.logError((`${BIDDER_CODE} : Incorrect JSON : bidRequest.nativeParams`)); + logError((`${BIDDER_CODE} : Incorrect JSON : bidRequest.nativeParams`)); } } @@ -221,10 +226,39 @@ function slotParams(bidRequest) { } else { params.ext.visibility = SLOT_VISIBILITY.NOT_DETERMINED; } + const floorInfo = getBidFloorByType(bidRequest); + if (floorInfo && floorInfo.length > 0) { + params.bidfloors = floorInfo; + } return params; } +function getBidFloorByType(bidRequest) { + let floorInfo = []; + if (typeof bidRequest.getFloor === 'function') { + [BANNER, VIDEO, NATIVE].forEach(mediaType => { + if (bidRequest.mediaTypes.hasOwnProperty(mediaType)) { + if (mediaType == BANNER) { + bidRequest.mediaTypes.banner.sizes.forEach( + size => { + setFloorInfo(bidRequest, mediaType, size, floorInfo) + } + ) + } else { + setFloorInfo(bidRequest, mediaType, '*', floorInfo) + } + } + }); + } + return floorInfo; +} +function setFloorInfo(bidRequest, mediaType, size, floorInfo) { + let floor = bidRequest.getFloor({currency: 'USD', mediaType: mediaType, size: size}); + if (size.length > 1) floor.size = size; + floor.mediaType = mediaType; + floorInfo.push(floor); +} function getMinSize(sizes) { return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); } @@ -285,8 +319,8 @@ function isValidBid(bid) { } function fetchCookieSyncUrls(response) { - if (!utils.isEmpty(response) && response[0].body && - response[0].body.ext && utils.isArray(response[0].body.ext.csUrl)) { + if (!isEmpty(response) && response[0].body && + response[0].body.ext && isArray(response[0].body.ext.csUrl)) { return response[0].body.ext.csUrl; } @@ -294,15 +328,15 @@ function fetchCookieSyncUrls(response) { } function getLoggingData(event, data) { - data = (utils.isArray(data) && data) || []; + data = (isArray(data) && data) || []; let params = {}; params.logid = 'kfk'; params.evtid = 'projectevents'; params.project = 'prebid'; - params.acid = utils.deepAccess(data, '0.auctionId') || ''; + params.acid = deepAccess(data, '0.auctionId') || ''; params.cid = $$PREBID_GLOBAL$$.medianetGlobals.cid || ''; - params.crid = data.map((adunit) => utils.deepAccess(adunit, 'params.0.crid') || adunit.adUnitCode).join('|'); + params.crid = data.map((adunit) => deepAccess(adunit, 'params.0.crid') || adunit.adUnitCode).join('|'); params.adunit_count = data.length || 0; params.dn = mnData.urlData.domain || ''; params.requrl = mnData.urlData.page || ''; @@ -320,7 +354,7 @@ function logEvent (event, data) { hostname: EVENT_PIXEL_URL, search: getLoggingData(event, data) }; - utils.triggerPixel(utils.buildUrl(getParams)); + triggerPixel(buildUrl(getParams)); } function clearMnData() { @@ -328,8 +362,8 @@ function clearMnData() { } function addRenderer(bid) { - const videoContext = utils.deepAccess(bid, 'context') || ''; - const vastTimeout = utils.deepAccess(bid, 'vto'); + const videoContext = deepAccess(bid, 'context') || ''; + const vastTimeout = deepAccess(bid, 'vto'); /* Adding renderer only when the context is Outstream and the provider has responded with a renderer. */ @@ -355,7 +389,7 @@ function newVideoRenderer(bid) { mute: bid.mt } const adUnitCode = bid.dfp_id; - const divId = utils.getGptSlotInfoForAdUnitCode(adUnitCode).divId || adUnitCode; + const divId = getGptSlotInfoForAdUnitCode(adUnitCode).divId || adUnitCode; window.mnet.mediaNetoutstreamPlayer(bid, divId, obj); }); }); @@ -376,12 +410,12 @@ export const spec = { */ isBidRequestValid: function(bid) { if (!bid.params) { - utils.logError(`${BIDDER_CODE} : Missing bid parameters`); + logError(`${BIDDER_CODE} : Missing bid parameters`); return false; } - if (!bid.params.cid || !utils.isStr(bid.params.cid) || utils.isEmptyStr(bid.params.cid)) { - utils.logError(`${BIDDER_CODE} : cid should be a string`); + if (!bid.params.cid || !isStr(bid.params.cid) || isEmptyStr(bid.params.cid)) { + logError(`${BIDDER_CODE} : cid should be a string`); return false; } @@ -416,13 +450,13 @@ export const spec = { let validBids = []; if (!serverResponse || !serverResponse.body) { - utils.logInfo(`${BIDDER_CODE} : response is empty`); + logInfo(`${BIDDER_CODE} : response is empty`); return validBids; } let bids = serverResponse.body.bidList; - if (!utils.isArray(bids) || bids.length === 0) { - utils.logInfo(`${BIDDER_CODE} : no bids`); + if (!isArray(bids) || bids.length === 0) { + logInfo(`${BIDDER_CODE} : no bids`); return validBids; } validBids = bids.filter(bid => isValidBid(bid)); diff --git a/modules/medianetBidAdapter.md b/modules/medianetBidAdapter.md index b465d678fc9..fbe967122e9 100644 --- a/modules/medianetBidAdapter.md +++ b/modules/medianetBidAdapter.md @@ -79,17 +79,16 @@ var videoAdUnit = { mediaTypes: { video: { context: "instream", - playerSize: [640, 480] + playerSize: [640, 480], + mimes: ['video/mp4'], + placement: 1 } }, bids: [{ bidder: 'medianet', params: { cid: '8CUX0H51C', - video: { - mimes: ['video/mp4'], - placement: 1 - }, + crid: '776755783', // Site member is to be used only for testing site: { page: 'http://smoketesting.net/prebidtest/', @@ -111,8 +110,10 @@ var videoAdUnit = { code: 'video1', mediaTypes: { video: { - context: "outstream", - playerSize: [640, 480] + context: "outstream", + playerSize: [640, 480], + mimes: ['video/mp4'], + placement: 1 } }, /** @@ -128,10 +129,7 @@ var videoAdUnit = { bidder: 'medianet', params: { cid: '8CUX0H51C', - video: { - mimes: ['video/mp4'], - placement: 1 - }, + crid: '776755783', // Site member is to be used only for testing site: { page: 'http://smoketesting.net/prebidtest/', diff --git a/modules/medianetRtdProvider.js b/modules/medianetRtdProvider.js new file mode 100644 index 00000000000..cd86bf891f3 --- /dev/null +++ b/modules/medianetRtdProvider.js @@ -0,0 +1,112 @@ +import { isStr, isEmptyStr, logError, mergeDeep, isFn, insertElement } from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +const MODULE_NAME = 'medianet'; +const SOURCE = MODULE_NAME + 'rtd'; +const AD_UNIT_CODE_TARGETING_KEY = 'mnadc'; +const OPEN_RTB_FIELD = 'ortb2Imp'; + +const getClientUrl = (customerId, domain) => `https://warp.media.net/js/tags/prebidrtdclient.js?cid=${customerId}&dn=${domain}`; + +window.mnjs = window.mnjs || {}; +window.mnjs.que = window.mnjs.que || []; + +function init(config) { + const customerId = config.params && config.params.cid; + if (!customerId || !isStr(customerId) || isEmptyStr(customerId)) { + logError(`${SOURCE}: cid should be a string`); + return false; + } + + loadRtdScript(customerId); + executeCommand(() => window.mnjs.setData({ + module: 'iref', + name: 'initIRefresh', + data: {config, prebidGlobal: getGlobal()}, + }, SOURCE)); + return true; +} + +function getBidRequestData(requestBidsProps, callback, config, userConsent) { + executeCommand(() => { + let adUnits = getAdUnits(requestBidsProps.adUnits, requestBidsProps.adUnitCodes); + const request = window.mnjs.onPrebidRequestBid({requestBidsProps, config, userConsent}); + if (!request) { + callback(); + return; + } + const success = (adUnitProps, openRtbProps) => { + adUnits.forEach(adUnit => { + adUnit[OPEN_RTB_FIELD] = adUnit[OPEN_RTB_FIELD] || {}; + mergeDeep(adUnit[OPEN_RTB_FIELD], openRtbProps[adUnit.code]); + mergeDeep(adUnit, adUnitProps[adUnit.code]); + }); + callback(); + }; + const error = () => callback(); + request.onComplete(error, success); + }); +} + +function onAuctionInitEvent(auctionInit) { + executeCommand(() => window.mnjs.setData({ + module: 'iref', + name: 'auctionInit', + data: {auction: auctionInit}, + }, SOURCE)); +} + +function getTargetingData(adUnitCode) { + const adUnits = getAdUnits(undefined, adUnitCode); + let targetingData = {}; + if (window.mnjs.loaded && isFn(window.mnjs.getTargetingData)) { + targetingData = window.mnjs.getTargetingData(adUnitCode, adUnits, SOURCE) || {}; + } + const targeting = {}; + adUnitCode.forEach(adUnitCode => { + targeting[adUnitCode] = targeting[adUnitCode] || {}; + targetingData[adUnitCode] = targetingData[adUnitCode] || {}; + targeting[adUnitCode] = { + // we use this to find gpt slot => prebid ad unit + [AD_UNIT_CODE_TARGETING_KEY]: adUnitCode, + ...targetingData[adUnitCode] + }; + }); + return targeting; +} + +function executeCommand(command) { + window.mnjs.que.push(command); +} + +function loadRtdScript(customerId) { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.async = true; + script.src = getClientUrl(customerId, window.location.hostname); + insertElement(script, window.document, 'head'); +} + +function getAdUnits(adUnits, adUnitCodes) { + adUnits = adUnits || getGlobal().adUnits || []; + if (adUnitCodes && adUnitCodes.length) { + adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); + } + return adUnits; +} + +export const medianetRtdModule = { + name: MODULE_NAME, + init, + getBidRequestData, + onAuctionInitEvent, + getTargetingData, +}; + +function registerSubModule() { + submodule('realTimeData', medianetRtdModule); +} + +registerSubModule(); diff --git a/modules/medianetRtdProvider.md b/modules/medianetRtdProvider.md new file mode 100644 index 00000000000..ed74d3bf348 --- /dev/null +++ b/modules/medianetRtdProvider.md @@ -0,0 +1,38 @@ +## Overview + +Module Name: Media.net Realtime Module +Module Type: Rtd Provider +Maintainer: prebid-support@media.net + +# Description + +The module currently provisions Media.net's Intelligent Refresh configured by the publisher. + +### Intelligent Refresh + +Intelligent Refresh (IR) module lets publisher refresh their ad inventory without affecting page experience of visitors through configured criteria. The module optionally provides tracking of refresh inventory and appropriate targeting in GAM. Publisher configured criteria is fetched via an external JS payload. + +# Integration + +1) Build the Media.net Intelligent Refresh RTD module into the Prebid.js package with: + +``` +gulp build --modules=medianetRtdProvider +``` + +# Configurations + +2) Enable Media.net Real Time Module using `pbjs.setConfig` + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'medianet', + params: { + cid: '8CUX0H51C' + } + }] + } +}); +``` diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 84783eb0991..0b6f763988f 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -7,11 +7,11 @@ const BIDDER_CODE = 'mediasquare'; const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' const BIDDER_URL_TEST = 'https://bidder-test.mediasquare.fr/' const BIDDER_ENDPOINT_AUCTION = 'msq_prebid'; -const BIDDER_ENDPOINT_SYNC = 'cookie_sync'; const BIDDER_ENDPOINT_WINNING = 'winning'; export const spec = { code: BIDDER_CODE, + gvlid: 791, aliases: ['msq'], // short code supportedMediaTypes: [BANNER, NATIVE, VIDEO], /** @@ -32,10 +32,16 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { let codes = []; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; + let floor = {}; const test = config.getConfig('debug') ? 1 : 0; let adunitValue = null; Object.keys(validBidRequests).forEach(key => { adunitValue = validBidRequests[key]; + if (typeof adunitValue.getFloor === 'function') { + floor = adunitValue.getFloor({currency: 'EUR', mediaType: '*', size: '*'}); + } else { + floor = {}; + } codes.push({ owner: adunitValue.params.owner, code: adunitValue.params.code, @@ -43,7 +49,8 @@ export const spec = { bidId: adunitValue.bidId, auctionId: adunitValue.auctionId, transactionId: adunitValue.transactionId, - mediatypes: adunitValue.mediaTypes + mediatypes: adunitValue.mediaTypes, + floor: floor }); }); const payload = { @@ -101,8 +108,14 @@ export const spec = { mediasquare: { 'bidder': value['bidder'], 'code': value['code'] + }, + meta: { + 'advertiserDomains': value['adomain'] } }; + if ('match' in value) { + bidResponse['mediasquare']['match'] = value['match']; + } if ('native' in value) { bidResponse['native'] = value['native']; bidResponse['mediaType'] = 'native'; @@ -126,18 +139,11 @@ export const spec = { * @return {UserSync[]} The user syncs which should be dropped. */ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - let params = ''; - let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; if (typeof serverResponses === 'object' && serverResponses != null && serverResponses.length > 0 && serverResponses[0].hasOwnProperty('body') && serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') { return serverResponses[0].body.cookies; } else { - if (gdprConsent && typeof gdprConsent.consentString === 'string') { params += typeof gdprConsent.gdprApplies === 'boolean' ? `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}` : `&gdpr_consent=${gdprConsent.consentString}`; } - if (uspConsent && typeof uspConsent === 'string') { params += '&uspConsent=' + uspConsent } - return { - type: 'iframe', - url: endpoint + BIDDER_ENDPOINT_SYNC + '?type=iframe' + params - }; + return []; } }, @@ -153,12 +159,13 @@ export const spec = { if (bid.hasOwnProperty('mediasquare')) { if (bid['mediasquare'].hasOwnProperty('bidder')) { params.push('bidder=' + bid['mediasquare']['bidder']); } if (bid['mediasquare'].hasOwnProperty('code')) { params.push('code=' + bid['mediasquare']['code']); } + if (bid['mediasquare'].hasOwnProperty('match')) { params.push('match=' + bid['mediasquare']['match']); } }; for (let i = 0; i < paramsToSearchFor.length; i++) { if (bid.hasOwnProperty(paramsToSearchFor[i])) { params.push(paramsToSearchFor[i] + '=' + bid[paramsToSearchFor[i]]); } } if (params.length > 0) { params = '?' + params.join('&'); } - ajax(endpoint + BIDDER_ENDPOINT_WINNING + params, null); + ajax(endpoint + BIDDER_ENDPOINT_WINNING + params, null, undefined, {method: 'GET', withCredentials: true}); return true; } diff --git a/modules/merkleIdSystem.js b/modules/merkleIdSystem.js index 353cc45473d..352c2d074e8 100644 --- a/modules/merkleIdSystem.js +++ b/modules/merkleIdSystem.js @@ -5,45 +5,84 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js' -import {ajax} from '../src/ajax.js'; +import { logInfo, logError, logWarn } from '../src/utils.js'; +import * as ajaxLib from '../src/ajax.js'; import {submodule} from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; const MODULE_NAME = 'merkleId'; -const SESSION_COOKIE_NAME = '_svsid'; const ID_URL = 'https://id2.sv.rkdms.com/identity/'; +const DEFAULT_REFRESH = 7 * 3600; +const SESSION_COOKIE_NAME = '_svsid'; export const storage = getStorageManager(); function getSession(configParams) { let session = null; - if (typeof configParams.sv_session !== 'string') { + if (typeof configParams.sv_session === 'string') { session = configParams.sv_session; } else { - session = readCookie() || readFromLocalStorage(); + session = storage.getCookie(SESSION_COOKIE_NAME); } return session; } -function readCookie() { - return storage.cookiesAreEnabled() ? storage.getCookie(SESSION_COOKIE_NAME) : null; +function setCookie(name, value, expires) { + let expTime = new Date(); + expTime.setTime(expTime.getTime() + expires * 1000 * 60); + storage.setCookie(name, value, expTime.toUTCString()); } -function readFromLocalStorage() { - return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(SESSION_COOKIE_NAME) : null; +function setSession(storage, response) { + logInfo('Merkle setting session '); + if (response && response.c && response.c.value && typeof response.c.value === 'string') { + setCookie(SESSION_COOKIE_NAME, response.c.value, storage.expires); + } } function constructUrl(configParams) { const session = getSession(configParams); - let url = ID_URL + `?vendor=${configParams.vendor}&sv_cid=${configParams.sv_cid}&sv_domain=${configParams.sv_domain}&sv_pubid=${configParams.sv_pubid}`; + let url = configParams.endpoint + `?vendor=${configParams.vendor}&sv_cid=${configParams.sv_cid}&sv_domain=${configParams.sv_domain}&sv_pubid=${configParams.sv_pubid}`; if (session) { - url.append(`&sv_session=${session}`); + url = `${url}&sv_session=${session}`; } - utils.logInfo('Merkle url :' + url); + logInfo('Merkle url :' + url); return url; } +function generateId(configParams, configStorage) { + const url = constructUrl(configParams); + + const resp = function (callback) { + ajaxLib.ajaxBuilder()( + url, + response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + setSession(configStorage, responseObj) + logInfo('Merkle responseObj ' + JSON.stringify(responseObj)); + } catch (error) { + logError(error); + } + } + + const date = new Date().toUTCString(); + responseObj.date = date; + logInfo('Merkle responseObj with date ' + JSON.stringify(responseObj)); + callback(responseObj); + }, + error => { + logError(`${MODULE_NAME}: merkleId fetch encountered an error`, error); + callback(); + }, + {method: 'GET', withCredentials: true} + ); + }; + return resp; +} + /** @type {Submodule} */ export const merkleIdSubmodule = { /** @@ -59,8 +98,8 @@ export const merkleIdSubmodule = { */ decode(value) { const id = (value && value.pam_id && typeof value.pam_id.id === 'string') ? value.pam_id : undefined; - utils.logInfo('Merkle id ' + JSON.stringify(id)); - return id ? { 'merkleId': id } : undefined; + logInfo('Merkle id ' + JSON.stringify(id)); + return id ? {'merkleId': id} : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -70,56 +109,82 @@ export const merkleIdSubmodule = { * @returns {IdResponse|undefined} */ getId(config, consentData) { + logInfo('User ID - merkleId generating id'); + const configParams = (config && config.params) || {}; + if (!configParams || typeof configParams.vendor !== 'string') { - utils.logError('User ID - merkleId submodule requires a valid vendor to be defined'); + logError('User ID - merkleId submodule requires a valid vendor to be defined'); return; } if (typeof configParams.sv_cid !== 'string') { - utils.logError('User ID - merkleId submodule requires a valid sv_cid string to be defined'); + logError('User ID - merkleId submodule requires a valid sv_cid string to be defined'); return; } if (typeof configParams.sv_pubid !== 'string') { - utils.logError('User ID - merkleId submodule requires a valid sv_pubid string to be defined'); + logError('User ID - merkleId submodule requires a valid sv_pubid string to be defined'); return; } - if (typeof configParams.sv_domain !== 'string') { - utils.logError('User ID - merkleId submodule requires a valid sv_domain string to be defined'); + if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) { + logError('User ID - merkleId submodule does not currently handle consent strings'); return; } + if (typeof configParams.endpoint !== 'string') { + logWarn('User ID - merkleId submodule endpoint string is not defined'); + configParams.endpoint = ID_URL + } + + if (typeof configParams.sv_domain !== 'string') { + configParams.sv_domain = merkleIdSubmodule.findRootDomain(); + } + + const configStorage = (config && config.storage) || {}; + const resp = generateId(configParams, configStorage) + return {callback: resp}; + }, + extendId: function (config = {}, consentData, storedId) { + logInfo('User ID - merkleId stored id ' + storedId); + const configParams = (config && config.params) || {}; + + if (typeof configParams.endpoint !== 'string') { + logWarn('User ID - merkleId submodule endpoint string is not defined'); + configParams.endpoint = ID_URL + } if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) { - utils.logError('User ID - merkleId submodule does not currently handle consent strings'); + logError('User ID - merkleId submodule does not currently handle consent strings'); return; } - const url = constructUrl(configParams); - - const resp = function (callback) { - const callbacks = { - success: response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - utils.logInfo('Merkle responseObj ' + JSON.stringify(responseObj)); - } catch (error) { - utils.logError(error); - } - } - callback(responseObj); - }, - error: error => { - utils.logError(`${MODULE_NAME}: merkleId fetch encountered an error`, error); - callback(); - } - }; - ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); - }; - return {callback: resp}; + + if (typeof configParams.sv_domain !== 'string') { + configParams.sv_domain = merkleIdSubmodule.findRootDomain(); + } + const configStorage = (config && config.storage) || {}; + if (configStorage && configStorage.refreshInSeconds && typeof configParams.refreshInSeconds === 'number') { + return {id: storedId}; + } + let refreshInSeconds = DEFAULT_REFRESH; + if (configParams && configParams.refreshInSeconds && typeof configParams.refreshInSeconds === 'number') { + refreshInSeconds = configParams.refreshInSeconds; + logInfo('User ID - merkleId param refreshInSeconds' + refreshInSeconds); + } + const storedDate = new Date(storedId.date); + let refreshNeeded = false; + if (storedDate) { + refreshNeeded = storedDate && (Date.now() - storedDate.getTime() > refreshInSeconds * 1000); + if (refreshNeeded) { + logInfo('User ID - merkleId needs refreshing id'); + const resp = generateId(configParams, configStorage) + return {callback: resp}; + } + } + logInfo('User ID - merkleId not refreshed'); + return {id: storedId}; } + }; submodule('userId', merkleIdSubmodule); diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 957b9a1d703..c811a0b2981 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -1,12 +1,13 @@ +import { _each, deepAccess, isPlainObject, isArray, isStr, logInfo, parseUrl, isEmpty, triggerPixel, logWarn, getBidIdParameter, isFn, isNumber } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); +const GVLID = 358; const DEFAULT_CUR = 'USD'; const BIDDER_CODE = 'mgid'; +export const storage = getStorageManager(GVLID, BIDDER_CODE); const ENDPOINT_URL = 'https://prebid.mgid.com/prebid/'; const LOG_WARN_PREFIX = '[MGID warn]: '; const LOG_INFO_PREFIX = '[MGID info]: '; @@ -57,12 +58,12 @@ let _NATIVE_ASSET_ID_TO_KEY_MAP = {}; let _NATIVE_ASSET_KEY_TO_ASSET_MAP = {}; // loading _NATIVE_ASSET_ID_TO_KEY_MAP -utils._each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAsset.KEY }); +_each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAsset.KEY }); // loading _NATIVE_ASSET_KEY_TO_ASSET_MAP -utils._each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); +_each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); export const spec = { - VERSION: '1.4', + VERSION: '1.5', code: BIDDER_CODE, supportedMediaTypes: [BANNER, NATIVE], reId: /^[1-9][0-9]*$/, @@ -75,20 +76,20 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: (bid) => { - const banner = utils.deepAccess(bid, 'mediaTypes.banner'); - const native = utils.deepAccess(bid, 'mediaTypes.native'); - let nativeOk = utils.isPlainObject(native); + const banner = deepAccess(bid, 'mediaTypes.banner'); + const native = deepAccess(bid, 'mediaTypes.native'); + let nativeOk = isPlainObject(native); if (nativeOk) { - const nativeParams = utils.deepAccess(bid, 'nativeParams'); + const nativeParams = deepAccess(bid, 'nativeParams'); let assetsCount = 0; - if (utils.isPlainObject(nativeParams)) { + if (isPlainObject(nativeParams)) { for (let k in nativeParams) { let v = nativeParams[k]; const supportProp = spec.NATIVE_ASSET_KEY_TO_ASSET_MAP.hasOwnProperty(k); if (supportProp) { assetsCount++ } - if (!utils.isPlainObject(v) || (!supportProp && utils.deepAccess(v, 'required'))) { + if (!isPlainObject(v) || (!supportProp && deepAccess(v, 'required'))) { nativeOk = false; break; } @@ -96,17 +97,17 @@ export const spec = { } nativeOk = nativeOk && (assetsCount > 0); } - let bannerOk = utils.isPlainObject(banner); + let bannerOk = isPlainObject(banner); if (bannerOk) { - const sizes = utils.deepAccess(banner, 'sizes'); - bannerOk = utils.isArray(sizes) && sizes.length > 0; + const sizes = deepAccess(banner, 'sizes'); + bannerOk = isArray(sizes) && sizes.length > 0; for (let f = 0; bannerOk && f < sizes.length; f++) { bannerOk = sizes[f].length === 2; } } let acc = Number(bid.params.accountId); let plcmt = Number(bid.params.placementId); - return (bannerOk || nativeOk) && utils.isPlainObject(bid.params) && !!bid.adUnitCode && utils.isStr(bid.adUnitCode) && (plcmt > 0 ? bid.params.placementId.toString().search(spec.reId) === 0 : true) && + return (bannerOk || nativeOk) && isPlainObject(bid.params) && !!bid.adUnitCode && isStr(bid.adUnitCode) && (plcmt > 0 ? bid.params.placementId.toString().search(spec.reId) === 0 : true) && !!acc && acc > 0 && bid.params.accountId.toString().search(spec.reId) === 0; }, /** @@ -116,34 +117,37 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { - utils.logInfo(LOG_INFO_PREFIX + `buildRequests`); + logInfo(LOG_INFO_PREFIX + `buildRequests`); if (validBidRequests.length === 0) { return; } const info = pageInfo(); - const page = info.location || utils.deepAccess(bidderRequest, 'refererInfo.referer') || utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl'); - const hostname = utils.parseUrl(page).hostname; + const page = info.location || deepAccess(bidderRequest, 'refererInfo.referer') || deepAccess(bidderRequest, 'refererInfo.canonicalUrl'); + const hostname = parseUrl(page).hostname; let domain = extractDomainFromHost(hostname) || hostname; const accountId = setOnAny(validBidRequests, 'params.accountId'); const muid = getLocalStorageSafely('mgMuidn'); let url = (setOnAny(validBidRequests, 'params.bidUrl') || ENDPOINT_URL) + accountId; - if (utils.isStr(muid) && muid.length > 0) { + if (isStr(muid) && muid.length > 0) { url += '?muid=' + muid; } - const cur = [setOnAny(validBidRequests, 'params.currency') || setOnAny(validBidRequests, 'params.cur') || config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; + const cur = setOnAny(validBidRequests, 'params.currency') || setOnAny(validBidRequests, 'params.cur') || config.getConfig('currency.adServerCurrency') || DEFAULT_CUR; const secure = window.location.protocol === 'https:' ? 1 : 0; let imp = []; validBidRequests.forEach(bid => { - let tagid = utils.deepAccess(bid, 'params.placementId') || 0; + let tagid = deepAccess(bid, 'params.placementId') || 0; tagid = !tagid ? bid.adUnitCode : tagid + '/' + bid.adUnitCode; let impObj = { id: bid.bidId, tagid, secure, }; - const bidFloor = utils.deepAccess(bid, 'params.bidFloor') || utils.deepAccess(bid, 'params.bidfloor') || 0; - if (bidFloor && utils.isNumber(bidFloor)) { - impObj.bidfloor = bidFloor; + const floorData = getBidFloor(bid, cur); + if (floorData.floor) { + impObj.bidfloor = floorData.floor; + } + if (floorData.cur) { + impObj.bidfloorcur = floorData.cur } for (let mediaTypes in bid.mediaTypes) { switch (mediaTypes) { @@ -169,9 +173,9 @@ export const spec = { } let request = { - id: utils.deepAccess(bidderRequest, 'bidderRequestId'), + id: deepAccess(bidderRequest, 'bidderRequestId'), site: {domain, page}, - cur: cur, + cur: [cur], geo: {utcoffset: info.timeOffset}, device: { ua: navigator.userAgent, @@ -181,7 +185,7 @@ export const spec = { w: screen.width, language: getLanguage() }, - ext: {mgid_ver: spec.VERSION, prebid_ver: $$PREBID_GLOBAL$$.version}, + ext: {mgid_ver: spec.VERSION, prebid_ver: '$prebid.version$'}, imp }; if (bidderRequest && bidderRequest.gdprConsent) { @@ -191,7 +195,7 @@ export const spec = { if (info.referrer) { request.site.ref = info.referrer } - utils.logInfo(LOG_INFO_PREFIX + `buildRequest:`, request); + logInfo(LOG_INFO_PREFIX + `buildRequest:`, request); return { method: 'POST', url: url, @@ -205,36 +209,36 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: (serverResponse, bidRequests) => { - utils.logInfo(LOG_INFO_PREFIX + `interpretResponse`, serverResponse); - if (serverResponse == null || serverResponse.body == null || serverResponse.body === '' || !utils.isArray(serverResponse.body.seatbid) || !serverResponse.body.seatbid.length) { + logInfo(LOG_INFO_PREFIX + `interpretResponse`, serverResponse); + if (serverResponse == null || serverResponse.body == null || serverResponse.body === '' || !isArray(serverResponse.body.seatbid) || !serverResponse.body.seatbid.length) { return; } const returnedBids = []; - const muidn = utils.deepAccess(serverResponse.body, 'ext.muidn') - if (utils.isStr(muidn) && muidn.length > 0) { + const muidn = deepAccess(serverResponse.body, 'ext.muidn') + if (isStr(muidn) && muidn.length > 0) { setLocalStorageSafely('mgMuidn', muidn) } serverResponse.body.seatbid.forEach((bids) => { bids.bid.forEach((bid) => { const pbid = prebidBid(bid, serverResponse.body.cur); - if (pbid.mediaType === NATIVE && utils.isEmpty(pbid.native)) { + if (pbid.mediaType === NATIVE && isEmpty(pbid.native)) { return; } returnedBids.push(pbid); }) }); - utils.logInfo(LOG_INFO_PREFIX + `interpretedResponse`, returnedBids); + logInfo(LOG_INFO_PREFIX + `interpretedResponse`, returnedBids); return returnedBids; }, onBidWon: (bid) => { - const cpm = utils.deepAccess(bid, 'adserverTargeting.hb_pb') || ''; - if (utils.isStr(bid.nurl) && bid.nurl !== '') { + const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; + if (isStr(bid.nurl) && bid.nurl !== '') { bid.nurl = bid.nurl.replace( /\${AUCTION_PRICE}/, cpm ); - utils.triggerPixel(bid.nurl); + triggerPixel(bid.nurl); } if (bid.isBurl) { if (bid.mediaType === BANNER) { @@ -247,13 +251,13 @@ export const spec = { /\${AUCTION_PRICE}/, cpm ); - utils.triggerPixel(bid.burl); + triggerPixel(bid.burl); } } - utils.logInfo(LOG_INFO_PREFIX + `onBidWon`); + logInfo(LOG_INFO_PREFIX + `onBidWon`); }, getUserSyncs: (syncOptions, serverResponses) => { - utils.logInfo(LOG_INFO_PREFIX + `getUserSyncs`); + logInfo(LOG_INFO_PREFIX + `getUserSyncs`); } }; @@ -261,7 +265,7 @@ registerBidder(spec); function setOnAny(collection, key) { for (let i = 0, result; i < collection.length; i++) { - result = utils.deepAccess(collection[i], key); + result = deepAccess(collection[i], key); if (result) { return result; } @@ -274,7 +278,7 @@ function setOnAny(collection, key) { * @return Bid */ function prebidBid(serverBid, cur) { - if (!utils.isStr(cur) || cur === '') { + if (!isStr(cur) || cur === '') { cur = DEFAULT_CUR; } const bid = { @@ -291,7 +295,8 @@ function prebidBid(serverBid, cur) { ttl: serverBid.ttl || 300, nurl: serverBid.nurl || '', burl: serverBid.burl || '', - isBurl: utils.isStr(serverBid.burl) && serverBid.burl.length > 0, + isBurl: isStr(serverBid.burl) && serverBid.burl.length > 0, + meta: { advertiserDomains: (isArray(serverBid.adomain) && serverBid.adomain.length > 0 ? serverBid.adomain : []) }, }; setMediaType(serverBid, bid); switch (bid.mediaType) { @@ -305,7 +310,7 @@ function prebidBid(serverBid, cur) { } function setMediaType(bid, newBid) { - if (utils.deepAccess(bid, 'ext.crtype') === 'native') { + if (deepAccess(bid, 'ext.crtype') === 'native') { newBid.mediaType = NATIVE; } else { newBid.mediaType = BANNER; @@ -359,7 +364,7 @@ function setLocalStorageSafely(key, val) { } function createBannerRequest(bid) { - const sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes'); + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes'); let format = []; if (sizes.length > 1) { for (let f = 0; f < sizes.length; f++) { @@ -375,6 +380,10 @@ function createBannerRequest(bid) { if (format.length) { r.format = format } + const pos = deepAccess(bid, 'mediaTypes.banner.pos') || 0 + if (pos) { + r.pos = pos + } return r } @@ -398,15 +407,15 @@ function createNativeRequest(params) { }; break; case NATIVE_ASSETS.IMAGE.KEY: - const wmin = params[key].wmin || params[key].minimumWidth || (utils.isArray(params[key].minsizes) && params[key].minsizes.length > 0 ? params[key].minsizes[0] : 0); - const hmin = params[key].hmin || params[key].minimumHeight || (utils.isArray(params[key].minsizes) && params[key].minsizes.length > 1 ? params[key].minsizes[1] : 0); + const wmin = params[key].wmin || params[key].minimumWidth || (isArray(params[key].minsizes) && params[key].minsizes.length > 0 ? params[key].minsizes[0] : 0); + const hmin = params[key].hmin || params[key].minimumHeight || (isArray(params[key].minsizes) && params[key].minsizes.length > 1 ? params[key].minsizes[1] : 0); assetObj = { id: NATIVE_ASSETS.IMAGE.ID, required: params[key].required ? 1 : 0, img: { type: NATIVE_ASSET_IMAGE_TYPE.IMAGE, - w: params[key].w || params[key].width || (utils.isArray(params[key].sizes) && params[key].sizes.length > 0 ? params[key].sizes[0] : 0), - h: params[key].h || params[key].height || (utils.isArray(params[key].sizes) && params[key].sizes.length > 1 ? params[key].sizes[1] : 0), + w: params[key].w || params[key].width || (isArray(params[key].sizes) && params[key].sizes.length > 0 ? params[key].sizes[0] : 0), + h: params[key].h || params[key].height || (isArray(params[key].sizes) && params[key].sizes.length > 1 ? params[key].sizes[1] : 0), mimes: params[key].mimes, ext: params[key].ext, } @@ -430,8 +439,8 @@ function createNativeRequest(params) { required: params[key].required ? 1 : 0, img: { type: NATIVE_ASSET_IMAGE_TYPE.ICON, - w: params[key].w || params[key].width || (utils.isArray(params[key].sizes) && params[key].sizes.length > 0 ? params[key].sizes[0] : 0), - h: params[key].h || params[key].height || (utils.isArray(params[key].sizes) && params[key].sizes.length > 0 ? params[key].sizes[1] : 0), + w: params[key].w || params[key].width || (isArray(params[key].sizes) && params[key].sizes.length > 0 ? params[key].sizes[0] : 0), + h: params[key].h || params[key].height || (isArray(params[key].sizes) && params[key].sizes.length > 0 ? params[key].sizes[1] : 0), } }; if (!assetObj.img.w) { @@ -476,7 +485,7 @@ function createNativeRequest(params) { break; } else { if (ele.id === 4 && nativeRequestObject.assets[i].id === 11) { - if (utils.deepAccess(nativeRequestObject.assets[i], 'data.type') === ele.data.type) { + if (deepAccess(nativeRequestObject.assets[i], 'data.type') === ele.data.type) { presentrequiredAssetCount++; break; } @@ -508,7 +517,7 @@ function parseNativeResponse(bid, newBid) { try { adm = JSON.parse(bid.adm); } catch (ex) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native response for ad response: ' + newBid.adm); + logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native response for ad response: ' + newBid.adm); return; } if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { @@ -574,3 +583,35 @@ function pageInfo() { timeOffset: t.getTimezoneOffset() }; } + +/** + * Get the floor price from bid.params for backward compatibility. + * If not found, then check floor module. + * @param bid A valid bid object + * @returns {*|number} floor price + */ +function getBidFloor(bid, cur) { + let bidFloor = getBidIdParameter('bidfloor', bid.params) || getBidIdParameter('bidFloor', bid.params) || 0; + const reqCur = cur + + if (!bidFloor && isFn(bid.getFloor)) { + const floorObj = bid.getFloor({ + currency: '*', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floorObj) && isNumber(floorObj.floor)) { + if (!floorObj.currency && reqCur !== DEFAULT_CUR) { + floorObj.currency = DEFAULT_CUR + } + if (floorObj.currency && reqCur !== floorObj.currency) { + cur = floorObj.currency + } + bidFloor = floorObj.floor; + } + } + if (reqCur === cur) { + cur = '' + } + return {floor: bidFloor, cur: cur} +} diff --git a/modules/microadBidAdapter.js b/modules/microadBidAdapter.js index 9611946b495..982bd61840a 100644 --- a/modules/microadBidAdapter.js +++ b/modules/microadBidAdapter.js @@ -1,3 +1,4 @@ +import { deepAccess, isEmpty, isStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; @@ -81,6 +82,11 @@ export const spec = { } } + const idlEnv = deepAccess(bid, 'userId.idl_env') + if (!isEmpty(idlEnv) && isStr(idlEnv)) { + params['idl_env'] = idlEnv + } + requests.push({ method: 'GET', url: ENDPOINT_URLS[ENVIRONMENT], @@ -105,6 +111,7 @@ export const spec = { creativeId: body.creativeId, netRevenue: body.netRevenue, currency: body.currency, + meta: body.meta || { advertiserDomains: [] } }; if (body.dealId) { diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 2b1d6bdb118..30749e977a8 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { formatQS, logInfo } from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -46,12 +46,7 @@ export const spec = { return { method: 'POST', - url: - ENDPOINT_URL + - '?' + - utils.formatQS({ - t: bidRequest.params.apiKey, - }), + url: ENDPOINT_URL + '?' + formatQS({ t: bidRequest.params.apiKey }), data: JSON.stringify(payload), }; }); @@ -79,7 +74,7 @@ export const spec = { * @param {data} Containing timeout specific data */ onTimeout: function onTimeout(timeoutData) { - utils.logInfo('Missena - Timeout from adapter', timeoutData); + logInfo('Missena - Timeout from adapter', timeoutData); }, /** @@ -87,7 +82,7 @@ export const spec = { * @param {Bid} The bid that won the auction */ onBidWon: function (bid) { - utils.logInfo('Missena - Bid won', bid); + logInfo('Missena - Bid won', bid); }, }; diff --git a/modules/mobfoxBidAdapter.js b/modules/mobfoxBidAdapter.js deleted file mode 100644 index 7c356e71089..00000000000 --- a/modules/mobfoxBidAdapter.js +++ /dev/null @@ -1,133 +0,0 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const utils = require('../src/utils.js'); -const BIDDER_CODE = 'mobfox'; -const BID_REQUEST_BASE_URL = 'https://my.mobfox.com/request.php'; -const CPM_HEADER = 'X-Pricing-CPM'; - -export const spec = { - code: BIDDER_CODE, - aliases: ['mf'], // short code - isBidRequestValid: function (bid) { - return bid.params.s !== null && bid.params.s !== undefined; - }, - buildRequests: function (validBidRequests) { - if (validBidRequests.length > 1) { - throw ('invalid number of valid bid requests, expected 1 element') - } - - let bidParams = validBidRequests[0].params; - let bid = validBidRequests[0]; - - let params = { - // -------------------- Mandatory Parameters ------------------ - rt: bidParams.rt || 'api-fetchip', - r_type: bidParams.r_type || 'banner', - r_resp: bidParams.r_resp || 'json', // string | vast20 - // i: bidParams.i || undefined , // string | 69.197.148.18 - s: bidParams.s, // string | 80187188f458cfde788d961b6882fd53 - u: bidParams.u || window.navigator.userAgent, // string - - // ------------------- Global Parameters ---------------------- - adspace_width: bidParams.adspace_width || bid.sizes[0][0], // integer | 320 - adspace_height: bidParams.adspace_height || bid.sizes[0][1], // integer | 48 - r_floor: bidParams.r_floor || undefined, // 0.8 - - o_andadvid: bidParams.o_andadvid || undefined, // 'c6292267-56ad-4326-965d-deef6fcd5er9' - longitude: bidParams.longitude || undefined, // 12.12 - latitude: bidParams.latitude || undefined, // 280.12 - demo_age: bidParams.demo_age || undefined, // 1978 - - // ------------------- banner / interstitial ---------------------- - adspace_strict: bidParams.adspace_strict || undefined, - - // ------------------- interstitial / video ---------------------- - imp_instl: bidParams.imp_instl || undefined, // integer | 1 - - // ------------------- mraid ---------------------- - c_mraid: bidParams.c_mraid || undefined, // integer | 1 - - // ------------------- video ---------------------- - v_dur_min: bidParams.v_dur_min || undefined, // integer | 0 - v_dur_max: bidParams.v_dur_max || undefined, // integer | 999 - v_autoplay: bidParams.v_autoplay || undefined, // integer | 1 - v_startmute: bidParams.v_startmute || undefined, // integer | 0 - v_rewarded: bidParams.v_rewarded || undefined, // integer | 0 - v_api: bidParams.v_api || undefined, // string | vpaid20 - n_ver: bidParams.n_ver || undefined, // - n_adunit: bidParams.n_adunit || undefined, // - n_layout: bidParams.n_layout || undefined, // - n_context: bidParams.n_context || undefined, // - n_plcmttype: bidParams.n_plcmttype || undefined, // - n_img_icon_req: bidParams.n_img_icon_req || undefined, // boolean0 - n_img_icon_size: bidParams.n_img_icon_size || undefined, // string80 - n_img_large_req: bidParams.n_img_large_req || undefined, // boolean0 - n_img_large_w: bidParams.n_img_large_w || undefined, // integer1200 - n_img_large_h: bidParams.n_img_large_h || undefined, // integer627 - n_title_req: bidParams.n_title_req || undefined, // boolean0 - n_title_len: bidParams.n_title_len || undefined, // string25 - n_desc_req: bidParams.n_desc_req || undefined, // boolean0 - n_desc_len: bidParams.n_desc_len || undefined, // string140 - n_rating_req: bidParams.n_rating_req || undefined - }; - - let payloadString = buildPayloadString(params); - - return { - method: 'GET', - url: BID_REQUEST_BASE_URL, - data: payloadString, - requestId: bid.bidId - }; - }, - interpretResponse: function (serverResponse, bidRequest) { - const bidResponses = []; - let serverResponseBody = serverResponse.body; - - if (!serverResponseBody || serverResponseBody.error) { - let errorMessage = `in response for ${BIDDER_CODE} adapter`; - if (serverResponseBody && serverResponseBody.error) { - errorMessage += `: ${serverResponseBody.error}`; - } - utils.logError(errorMessage); - return bidResponses; - } - try { - let serverResponseHeaders = serverResponse.headers; - let bidRequestData = bidRequest.data.split('&'); - const bidResponse = { - requestId: bidRequest.requestId, - cpm: serverResponseHeaders.get(CPM_HEADER), - width: bidRequestData[5].split('=')[1], - height: bidRequestData[6].split('=')[1], - creativeId: bidRequestData[3].split('=')[1], - currency: 'USD', - netRevenue: true, - ttl: 360, - referrer: serverResponseBody.request.clickurl, - ad: serverResponseBody.request.htmlString - }; - bidResponses.push(bidResponse); - } catch (e) { - throw 'could not build bid response: ' + e; - } - return bidResponses; - } -}; - -function buildPayloadString(params) { - for (let key in params) { - if (params.hasOwnProperty(key)) { - if (params[key] === undefined) { - delete params[key]; - } else { - params[key] = encodeURIComponent(params[key]); - } - } - } - - return utils._map(Object.keys(params), key => `${key}=${params[key]}`) - .join('&') -} - -registerBidder(spec); diff --git a/modules/mobfoxpbBidAdapter.js b/modules/mobfoxpbBidAdapter.js deleted file mode 100644 index c7e96b95179..00000000000 --- a/modules/mobfoxpbBidAdapter.js +++ /dev/null @@ -1,99 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; - -const BIDDER_CODE = 'mobfoxpb'; -const AD_URL = 'https://bes.mobfox.com/?c=o&m=multi'; - -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; - } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers); - default: - return false; - } -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], - - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - const winTop = utils.getWindowTop(); - const location = winTop.location; - const placements = []; - const request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent - } - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - const placement = { - placementId: bid.params.placementId, - bidId: bid.bidId, - schain: bid.schain || {}, - }; - const mediaType = bid.mediaTypes - - if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { - placement.sizes = mediaType[BANNER].sizes; - placement.traffic = BANNER; - } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { - placement.wPlayer = mediaType[VIDEO].playerSize[0]; - placement.hPlayer = mediaType[VIDEO].playerSize[1]; - placement.traffic = VIDEO; - } else if (mediaType && mediaType[NATIVE]) { - placement.native = mediaType[NATIVE]; - placement.traffic = NATIVE; - } - placements.push(placement); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - response.push(resItem); - } - } - return response; - }, -}; - -registerBidder(spec); diff --git a/modules/mobsmartBidAdapter.js b/modules/mobsmartBidAdapter.js deleted file mode 100644 index e5ff38ec69a..00000000000 --- a/modules/mobsmartBidAdapter.js +++ /dev/null @@ -1,94 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; - -const BIDDER_CODE = 'mobsmart'; -const ENDPOINT = 'https://prebid.mobsmart.net/prebid/endpoint'; - -export const spec = { - code: BIDDER_CODE, - isBidRequestValid: function(bid) { - if (bid.bidder !== BIDDER_CODE) { - return false; - } - - return true; - }, - buildRequests: function(validBidRequests, bidderRequest) { - const timeout = config.getConfig('bidderTimeout'); - const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); - - return validBidRequests.map(bidRequest => { - const adUnit = { - code: bidRequest.adUnitCode, - bids: { - bidder: bidRequest.bidder, - params: bidRequest.params - }, - mediaTypes: bidRequest.mediaTypes - }; - - if (bidRequest.hasOwnProperty('sizes') && bidRequest.sizes.length > 0) { - adUnit.sizes = bidRequest.sizes; - } - - const request = { - auctionId: bidRequest.auctionId, - requestId: bidRequest.bidId, - bidRequestsCount: bidRequest.bidRequestsCount, - bidderRequestId: bidRequest.bidderRequestId, - transactionId: bidRequest.transactionId, - referrer: referrer, - timeout: timeout, - adUnit: adUnit - }; - - if (bidRequest.userId && bidRequest.userId.pubcid) { - request.userId = {pubcid: bidRequest.userId.pubcid}; - } - - return { - method: 'POST', - url: ENDPOINT, - data: JSON.stringify(request) - } - }); - }, - interpretResponse: function(serverResponse) { - const bidResponses = []; - - if (serverResponse.body) { - const response = serverResponse.body; - const bidResponse = { - requestId: response.requestId, - cpm: response.cpm, - width: response.width, - height: response.height, - creativeId: response.creativeId, - currency: response.currency, - netRevenue: response.netRevenue, - ttl: response.ttl, - ad: response.ad, - }; - bidResponses.push(bidResponse); - } - - return bidResponses; - }, - getUserSyncs: function(syncOptions, serverResponses) { - let syncs = []; - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: 'https://tags.mobsmart.net/tags/iframe' - }); - } else if (syncOptions.pixelEnabled) { - syncs.push({ - type: 'image', - url: 'https://tags.mobsmart.net/tags/image' - }); - } - - return syncs; - } -} -registerBidder(spec); diff --git a/modules/multibid/index.js b/modules/multibid/index.js index dd4999b2dca..ef0771e291f 100644 --- a/modules/multibid/index.js +++ b/modules/multibid/index.js @@ -5,7 +5,9 @@ import {config} from '../../src/config.js'; import {setupBeforeHookFnOnce, getHook} from '../../src/hook.js'; -import * as utils from '../../src/utils.js'; +import { + logWarn, deepAccess, getUniqueIdentifierStr, deepSetValue, groupBy +} from '../../src/utils.js'; import events from '../../src/events.js'; import CONSTANTS from '../../src/constants.json'; import {addBidderRequests} from '../../src/auction.js'; @@ -50,7 +52,7 @@ export function validateMultibid(conf) { let duplicate = conf.filter(entry => { // Check if entry.bidder is not defined or typeof string, filter entry and reset configuration if ((!entry.bidder || typeof entry.bidder !== 'string') && (!entry.bidders || !Array.isArray(entry.bidders))) { - utils.logWarn('Filtering multibid entry. Missing required bidder or bidders property.'); + logWarn('Filtering multibid entry. Missing required bidder or bidders property.'); check = false; return false; } @@ -97,26 +99,26 @@ export function adjustBidderRequestsHook(fn, bidderRequests) { * @param {Object} bid object */ export function addBidResponseHook(fn, adUnitCode, bid) { - let floor = utils.deepAccess(bid, 'floorData.floorValue'); + let floor = deepAccess(bid, 'floorData.floorValue'); if (!config.getConfig('multibid')) resetMultiConfig(); // Checks if multiconfig exists and bid bidderCode exists within config and is an adpod bid // Else checks if multiconfig exists and bid bidderCode exists within config // Else continue with no modifications - if (hasMultibid && multiConfig[bid.bidderCode] && utils.deepAccess(bid, 'video.context') === 'adpod') { + if (hasMultibid && multiConfig[bid.bidderCode] && deepAccess(bid, 'video.context') === 'adpod') { fn.call(this, adUnitCode, bid); } else if (hasMultibid && multiConfig[bid.bidderCode]) { // Set property multibidPrefix on bid if (multiConfig[bid.bidderCode].prefix) bid.multibidPrefix = multiConfig[bid.bidderCode].prefix; bid.originalBidder = bid.bidderCode; // Check if stored bids for auction include adUnitCode.bidder and max limit not reach for ad unit - if (utils.deepAccess(multibidUnits, `${adUnitCode}.${bid.bidderCode}`)) { + if (deepAccess(multibidUnits, [adUnitCode, bid.bidderCode])) { // Store request id under new property originalRequestId, create new unique bidId, // and push bid into multibid stored bids for auction if max not reached and bid cpm above floor if (!multibidUnits[adUnitCode][bid.bidderCode].maxReached && (!floor || floor <= bid.cpm)) { bid.originalRequestId = bid.requestId; - bid.requestId = utils.getUniqueIdentifierStr(); + bid.requestId = getUniqueIdentifierStr(); multibidUnits[adUnitCode][bid.bidderCode].ads.push(bid); let length = multibidUnits[adUnitCode][bid.bidderCode].ads.length; @@ -126,12 +128,12 @@ export function addBidResponseHook(fn, adUnitCode, bid) { fn.call(this, adUnitCode, bid); } else { - utils.logWarn(`Filtering multibid received from bidder ${bid.bidderCode}: ` + ((multibidUnits[adUnitCode][bid.bidderCode].maxReached) ? `Maximum bid limit reached for ad unit code ${adUnitCode}` : 'Bid cpm under floors value.')); + logWarn(`Filtering multibid received from bidder ${bid.bidderCode}: ` + ((multibidUnits[adUnitCode][bid.bidderCode].maxReached) ? `Maximum bid limit reached for ad unit code ${adUnitCode}` : 'Bid cpm under floors value.')); } } else { - if (utils.deepAccess(bid, 'floorData.floorValue')) utils.deepSetValue(multibidUnits, `${adUnitCode}.${bid.bidderCode}`, {floor: utils.deepAccess(bid, 'floorData.floorValue')}); + if (deepAccess(bid, 'floorData.floorValue')) deepSetValue(multibidUnits, [adUnitCode, bid.bidderCode], {floor: deepAccess(bid, 'floorData.floorValue')}); - utils.deepSetValue(multibidUnits, `${adUnitCode}.${bid.bidderCode}`, {ads: [bid]}); + deepSetValue(multibidUnits, [adUnitCode, bid.bidderCode], {ads: [bid]}); if (multibidUnits[adUnitCode][bid.bidderCode].ads.length === multiConfig[bid.bidderCode].maxbids) multibidUnits[adUnitCode][bid.bidderCode].maxReached = true; fn.call(this, adUnitCode, bid); @@ -170,11 +172,11 @@ export function targetBidPoolHook(fn, bidsReceived, highestCpmCallback, adUnitBi if (hasMultibid) { const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization'); let modifiedBids = []; - let buckets = utils.groupBy(bidsReceived, 'adUnitCode'); + let buckets = groupBy(bidsReceived, 'adUnitCode'); let bids = [].concat.apply([], Object.keys(buckets).reduce((result, slotId) => { let bucketBids = []; // Get bids and group by property originalBidder - let bidsByBidderName = utils.groupBy(buckets[slotId], 'originalBidder'); + let bidsByBidderName = groupBy(buckets[slotId], 'originalBidder'); let adjustedBids = [].concat.apply([], Object.keys(bidsByBidderName).map(key => { // Reset all bidderCodes to original bidder values and sort by CPM return bidsByBidderName[key].sort((bidA, bidB) => { @@ -183,7 +185,7 @@ export function targetBidPoolHook(fn, bidsReceived, highestCpmCallback, adUnitBi return bidA.cpm > bidB.cpm ? -1 : (bidA.cpm < bidB.cpm ? 1 : 0); }).map((bid, index) => { // For each bid (post CPM sort), set dynamic bidderCode using prefix and index if less than maxbid amount - if (utils.deepAccess(multiConfig, `${bid.bidderCode}.prefix`) && index !== 0 && index < multiConfig[bid.bidderCode].maxbids) { + if (deepAccess(multiConfig, `${bid.bidderCode}.prefix`) && index !== 0 && index < multiConfig[bid.bidderCode].maxbids) { bid.bidderCode = multiConfig[bid.bidderCode].prefix + (index + 1); } @@ -191,7 +193,7 @@ export function targetBidPoolHook(fn, bidsReceived, highestCpmCallback, adUnitBi }) })); // Get adjustedBids by bidderCode and reduce using highestCpmCallback - let bidsByBidderCode = utils.groupBy(adjustedBids, 'bidderCode'); + let bidsByBidderCode = groupBy(adjustedBids, 'bidderCode'); Object.keys(bidsByBidderCode).forEach(key => bucketBids.push(bidsByBidderCode[key].reduce(highestCpmCallback))); // if adUnitBidLimit is set, pass top N number bids if (adUnitBidLimit > 0) { diff --git a/modules/mwOpenLinkIdSystem.js b/modules/mwOpenLinkIdSystem.js index b2381836d5d..552223fa73c 100644 --- a/modules/mwOpenLinkIdSystem.js +++ b/modules/mwOpenLinkIdSystem.js @@ -5,7 +5,7 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js'; +import { timestamp, logError, deepClone, generateUUID, isPlainObject } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -18,20 +18,20 @@ const openLinkID = { const storage = getStorageManager(); function getExpirationDate() { - return (new Date(utils.timestamp() + openLinkID.cookie_expiration)).toGMTString(); + return (new Date(timestamp() + openLinkID.cookie_expiration)).toGMTString(); } function isValidConfig(configParams) { if (!configParams) { - utils.logError('User ID - mwOlId submodule requires configParams'); + logError('User ID - mwOlId submodule requires configParams'); return false; } if (!configParams.accountId) { - utils.logError('User ID - mwOlId submodule requires accountId to be defined'); + logError('User ID - mwOlId submodule requires accountId to be defined'); return false; } if (!configParams.partnerId) { - utils.logError('User ID - mwOlId submodule requires partnerId to be defined'); + logError('User ID - mwOlId submodule requires partnerId to be defined'); return false; } return true; @@ -96,7 +96,7 @@ function register(configParams, olid) { function setID(configParams) { if (!isValidConfig(configParams)) return undefined; const mwOlId = readCookie(); - const newMwOlId = mwOlId ? utils.deepClone(mwOlId) : {eid: utils.generateUUID()}; + const newMwOlId = mwOlId ? deepClone(mwOlId) : {eid: generateUUID()}; writeCookie(newMwOlId); register(configParams, newMwOlId.eid); return { @@ -122,7 +122,7 @@ export const mwOpenLinkIdSubModule = { * @return {(Object|undefined} */ decode(mwOlId) { - const id = mwOlId && utils.isPlainObject(mwOlId) ? mwOlId.eid : undefined; + const id = mwOlId && isPlainObject(mwOlId) ? mwOlId.eid : undefined; return id ? { 'mwOpenLinkId': id } : undefined; }, diff --git a/modules/mytargetBidAdapter.js b/modules/mytargetBidAdapter.js index bcf8e7ad17f..f55f2e6b802 100644 --- a/modules/mytargetBidAdapter.js +++ b/modules/mytargetBidAdapter.js @@ -1,9 +1,9 @@ -import * as utils from '../src/utils.js'; +import { _map } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'mytarget'; -const BIDDER_URL = 'https://ad.mail.ru/hbid_prebid/'; +const BIDDER_URL = '//ad.mail.ru/hbid_prebid/'; const DEFAULT_CURRENCY = 'RUB'; const DEFAULT_TTL = 180; @@ -28,18 +28,14 @@ function getSiteName(referrer) { let sitename = config.getConfig('mytarget.sitename'); if (!sitename) { - sitename = utils.parseUrl(referrer).hostname; + const parsed = document.createElement('a'); + parsed.href = decodeURIComponent(referrer); + sitename = parsed.hostname; } return sitename; } -function getCurrency() { - let currency = config.getConfig('currency.adServerCurrency'); - - return (currency === 'USD') ? currency : DEFAULT_CURRENCY; -} - function generateRandomId() { return Math.random().toString(16).substring(2); } @@ -59,13 +55,13 @@ export const spec = { } const payload = { - places: utils._map(validBidRequests, buildPlacement), + places: _map(validBidRequests, buildPlacement), site: { sitename: getSiteName(referrer), page: referrer }, settings: { - currency: getCurrency(), + currency: DEFAULT_CURRENCY, windowSize: { width: window.screen.width, height: window.screen.height @@ -84,7 +80,7 @@ export const spec = { let { body } = serverResponse; if (body.bids) { - return utils._map(body.bids, (bid) => { + return _map(body.bids, (bid) => { let bidResponse = { requestId: bid.id, cpm: bid.price, @@ -93,7 +89,10 @@ export const spec = { ttl: bid.ttl || DEFAULT_TTL, currency: bid.currency || DEFAULT_CURRENCY, creativeId: bid.creativeId || generateRandomId(), - netRevenue: true + netRevenue: true, + meta: { + advertiserDomains: bid.adomain && bid.adomain.length > 0 ? bid.adomain : [], + } } if (bid.adm) { diff --git a/modules/nafdigitalBidAdapter.js b/modules/nafdigitalBidAdapter.js deleted file mode 100644 index d64e079b52a..00000000000 --- a/modules/nafdigitalBidAdapter.js +++ /dev/null @@ -1,245 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; - -const BIDDER_CODE = 'nafdigital'; -const URL = 'https://nafdigitalbidder.com/hb'; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs -}; - -function buildRequests(bidReqs, bidderRequest) { - try { - let referrer = ''; - if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.referer; - } - const nafdigitalImps = []; - const publisherId = utils.getBidIdParameter('publisherId', bidReqs[0].params); - utils._each(bidReqs, function (bid) { - bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); - bid.sizes = bid.sizes.filter(size => utils.isArray(size)); - const processedSizes = bid.sizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); - - const element = document.getElementById(bid.adUnitCode); - const minSize = _getMinSize(processedSizes); - const viewabilityAmount = _isViewabilityMeasurable(element) - ? _getViewability(element, utils.getWindowTop(), minSize) - : 'na'; - const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); - - const imp = { - id: bid.bidId, - banner: { - format: processedSizes, - ext: { - viewability: viewabilityAmountRounded - } - }, - tagid: String(bid.adUnitCode) - }; - const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); - if (bidFloor) { - imp.bidfloor = bidFloor; - } - nafdigitalImps.push(imp); - }); - const nafdigitalBidReq = { - id: utils.getUniqueIdentifierStr(), - imp: nafdigitalImps, - site: { - domain: utils.parseUrl(referrer).host, - page: referrer, - publisher: { - id: publisherId - } - }, - device: { - devicetype: _getDeviceType(), - w: screen.width, - h: screen.height - }, - tmax: config.getConfig('bidderTimeout') - }; - - return { - method: 'POST', - url: URL, - data: JSON.stringify(nafdigitalBidReq), - options: {contentType: 'text/plain', withCredentials: false} - }; - } catch (e) { - utils.logError(e, {bidReqs, bidderRequest}); - } -} - -function isBidRequestValid(bid) { - if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { - return false; - } - - if (typeof bid.params.publisherId === 'undefined') { - return false; - } - - return true; -} - -function interpretResponse(serverResponse) { - if (!serverResponse.body || typeof serverResponse.body != 'object') { - utils.logWarn('NAF digital server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); - return []; - } - const { body: {id, seatbid} } = serverResponse; - try { - const nafdigitalBidResponses = []; - if (id && - seatbid && - seatbid.length > 0 && - seatbid[0].bid && - seatbid[0].bid.length > 0) { - seatbid[0].bid.map(nafdigitalBid => { - nafdigitalBidResponses.push({ - requestId: nafdigitalBid.impid, - cpm: parseFloat(nafdigitalBid.price), - width: parseInt(nafdigitalBid.w), - height: parseInt(nafdigitalBid.h), - creativeId: nafdigitalBid.crid || nafdigitalBid.id, - currency: 'USD', - netRevenue: true, - mediaType: BANNER, - ad: _getAdMarkup(nafdigitalBid), - ttl: 60 - }); - }); - } - return nafdigitalBidResponses; - } catch (e) { - utils.logError(e, {id, seatbid}); - } -} - -// Don't do user sync for now -function getUserSyncs(syncOptions, responses, gdprConsent) { - return []; -} - -function _isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); -} - -function _isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); -} - -function _getDeviceType() { - return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; -} - -function _getAdMarkup(bid) { - let adm = bid.adm; - if ('nurl' in bid) { - adm += utils.createTrackPixelHtml(bid.nurl); - } - return adm; -} - -function _isViewabilityMeasurable(element) { - return !_isIframe() && element !== null; -} - -function _getViewability(element, topWin, { w, h } = {}) { - return utils.getWindowTop().document.visibilityState === 'visible' - ? _getPercentInView(element, topWin, { w, h }) - : 0; -} - -function _isIframe() { - try { - return utils.getWindowSelf() !== utils.getWindowTop(); - } catch (e) { - return true; - } -} - -function _getMinSize(sizes) { - return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); -} - -function _getBoundingBox(element, { w, h } = {}) { - let { width, height, left, top, right, bottom } = element.getBoundingClientRect(); - - if ((width === 0 || height === 0) && w && h) { - width = w; - height = h; - right = left + w; - bottom = top + h; - } - - return { width, height, left, top, right, bottom }; -} - -function _getIntersectionOfRects(rects) { - const bbox = { - left: rects[0].left, - right: rects[0].right, - top: rects[0].top, - bottom: rects[0].bottom - }; - - for (let i = 1; i < rects.length; ++i) { - bbox.left = Math.max(bbox.left, rects[i].left); - bbox.right = Math.min(bbox.right, rects[i].right); - - if (bbox.left >= bbox.right) { - return null; - } - - bbox.top = Math.max(bbox.top, rects[i].top); - bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); - - if (bbox.top >= bbox.bottom) { - return null; - } - } - - bbox.width = bbox.right - bbox.left; - bbox.height = bbox.bottom - bbox.top; - - return bbox; -} - -function _getPercentInView(element, topWin, { w, h } = {}) { - const elementBoundingBox = _getBoundingBox(element, { w, h }); - - // Obtain the intersection of the element and the viewport - const elementInViewBoundingBox = _getIntersectionOfRects([ { - left: 0, - top: 0, - right: topWin.innerWidth, - bottom: topWin.innerHeight - }, elementBoundingBox ]); - - let elementInViewArea, elementTotalArea; - - if (elementInViewBoundingBox !== null) { - // Some or all of the element is in view - elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; - elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; - - return ((elementInViewArea / elementTotalArea) * 100); - } - - // No overlap between element and the viewport; therefore, the element - // lies completely out of view - return 0; -} - -registerBidder(spec); diff --git a/modules/nanointeractiveBidAdapter.js b/modules/nanointeractiveBidAdapter.js deleted file mode 100644 index 42a343efc03..00000000000 --- a/modules/nanointeractiveBidAdapter.js +++ /dev/null @@ -1,159 +0,0 @@ -import * as utils from '../src/utils.js'; -import {config} from '../src/config.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const storage = getStorageManager(); - -export const BIDDER_CODE = 'nanointeractive'; -export const END_POINT_URL = 'https://ad.audiencemanager.de'; - -export const SSP_PLACEMENT_ID = 'pid'; -export const NQ = 'nq'; -export const NQ_NAME = 'name'; -export const CATEGORY = 'category'; -export const CATEGORY_NAME = 'categoryName'; -export const SUB_ID = 'subId'; -export const REF = 'ref'; -export const LOCATION = 'loc'; - -var nanoPid = '5a1ec660eb0a191dfa591172'; - -export const spec = { - - code: BIDDER_CODE, - aliases: ['ni'], - - isBidRequestValid(bid) { - const pid = bid.params[SSP_PLACEMENT_ID]; - return !!(pid); - }, - - buildRequests(validBidRequests, bidderRequest) { - let payload = []; - validBidRequests.forEach( - bid => payload.push(createSingleBidRequest(bid, bidderRequest)) - ); - const url = getEndpointUrl() + '/hb'; - - return { - method: 'POST', - url: url, - data: JSON.stringify(payload) - }; - }, - interpretResponse(serverResponse) { - const bids = []; - serverResponse.body.forEach(serverBid => { - if (isEngineResponseValid(serverBid)) { - bids.push(createSingleBidResponse(serverBid)); - } - }); - return bids; - }, - getUserSyncs: function(syncOptions) { - const syncs = []; - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: getEndpointUrl() + '/hb/cookieSync/' + nanoPid - }); - } - - if (syncOptions.pixelEnabled) { - syncs.push({ - type: 'image', - url: getEndpointUrl() + '/hb/cookieSync/' + nanoPid - }); - } - return syncs; - } - -}; - -function createSingleBidRequest(bid, bidderRequest) { - const location = utils.deepAccess(bidderRequest, 'refererInfo.referer'); - const origin = utils.getOrigin(); - - nanoPid = bid.params[SSP_PLACEMENT_ID] || nanoPid; - - const data = { - [SSP_PLACEMENT_ID]: bid.params[SSP_PLACEMENT_ID], - [NQ]: [createNqParam(bid)], - [CATEGORY]: [createCategoryParam(bid)], - [SUB_ID]: createSubIdParam(bid), - [REF]: createRefParam(), - sizes: bid.sizes.map(value => value[0] + 'x' + value[1]), - bidId: bid.bidId, - cors: origin, - [LOCATION]: location, - lsUserId: getLsUserId() - }; - - if (bidderRequest && bidderRequest.gdprConsent) { - data['gdprConsent'] = bidderRequest.gdprConsent.consentString; - data['gdprApplies'] = (bidderRequest.gdprConsent.gdprApplies) ? '1' : '0'; - } - - return data; -} - -function createSingleBidResponse(serverBid) { - if (serverBid.userId) { - storage.setDataInLocalStorage('lsUserId', serverBid.userId); - } - return { - requestId: serverBid.id, - cpm: serverBid.cpm, - width: serverBid.width, - height: serverBid.height, - ad: serverBid.ad, - ttl: serverBid.ttl, - creativeId: serverBid.creativeId, - netRevenue: serverBid.netRevenue || true, - currency: serverBid.currency - }; -} - -function createNqParam(bid) { - return bid.params[NQ_NAME] ? utils.getParameterByName(bid.params[NQ_NAME]) : bid.params[NQ] || null; -} - -function createCategoryParam(bid) { - return bid.params[CATEGORY_NAME] ? utils.getParameterByName(bid.params[CATEGORY_NAME]) : bid.params[CATEGORY] || null; -} - -function createSubIdParam(bid) { - return bid.params[SUB_ID] || null; -} - -function createRefParam() { - try { - return window.top.document.referrer; - } catch (ex) { - return document.referrer; - } -} - -function isEngineResponseValid(response) { - return !!response.cpm && !!response.ad; -} - -/** - * Used mainly for debugging - * - * @returns string - */ -function getEndpointUrl() { - const nanoConfig = config.getConfig('nano'); - return (nanoConfig && nanoConfig['endpointUrl']) || END_POINT_URL; -} - -function getLsUserId() { - if (storage.getDataFromLocalStorage('lsUserId') != null) { - return storage.getDataFromLocalStorage('lsUserId'); - } - return null; -} - -registerBidder(spec); diff --git a/modules/nasmediaAdmixerBidAdapter.js b/modules/nasmediaAdmixerBidAdapter.js deleted file mode 100644 index fd7a7baa58a..00000000000 --- a/modules/nasmediaAdmixerBidAdapter.js +++ /dev/null @@ -1,85 +0,0 @@ - -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; - -const ADMIXER_ENDPOINT = 'https://adn.admixer.co.kr:10443/prebid/ad_req'; -const BIDDER_CODE = 'nasmediaAdmixer'; - -const DEFAULT_BID_TTL = 360; -const DEFAULT_CURRENCY = 'USD'; -const DEFAULT_REVENUE = false; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - - isBidRequestValid: function (bid) { - return !!(bid && bid.params && bid.params.media_key && bid.params.adunit_id); - }, - - buildRequests: function (validBidRequests, bidderRequest) { - return validBidRequests.map(bid => { - const payload = { - media_key: bid.params.media_key, - adunit_id: bid.params.adunit_id, - req_id: bid.bidId, - referrer: bidderRequest.refererInfo.referer, - os: getOs(), - platform: getPlatform(), - }; - - return { - method: 'GET', - url: ADMIXER_ENDPOINT, - data: payload, - } - }) - }, - - interpretResponse: function (serverResponse, bidRequest) { - const serverBody = serverResponse.body; - const bidResponses = []; - - if (serverBody && serverBody.error_code === 0 && serverBody.body && serverBody.body.length > 0) { - let bidData = serverBody.body[0]; - - const bidResponse = { - ad: bidData.ad, - requestId: serverBody.req_id, - creativeId: bidData.ad_id, - cpm: bidData.cpm, - width: bidData.width, - height: bidData.height, - currency: bidData.currency ? bidData.currency : DEFAULT_CURRENCY, - netRevenue: DEFAULT_REVENUE, - ttl: DEFAULT_BID_TTL - }; - - bidResponses.push(bidResponse); - } - return bidResponses; - } -} - -function getOs() { - let ua = navigator.userAgent; - if (ua.match(/(iPhone|iPod|iPad)/)) { - return 'ios'; - } else if (ua.match(/Android/)) { - return 'android'; - } else if (ua.match(/Window/)) { - return 'windows'; - } else { - return 'etc'; - } -} - -function getPlatform() { - return (isMobile()) ? 'm_web' : 'pc_web'; -} - -function isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent.toLowerCase()); -} - -registerBidder(spec); diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index d396bd4d495..c9e6a1f659f 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js' +import { deepAccess, isEmpty } from '../src/utils.js' import { registerBidder } from '../src/adapters/bidderFactory.js' import { BANNER } from '../src/mediaTypes.js' // import { config } from 'src/config' @@ -6,16 +6,30 @@ import { BANNER } from '../src/mediaTypes.js' const BIDDER_CODE = 'nativo' const BIDDER_ENDPOINT = 'https://exchange.postrelease.com/prebid' +const GVLID = 263 + const TIME_TO_LIVE = 360 const SUPPORTED_AD_TYPES = [BANNER] const bidRequestMap = {} +const adUnitsRequested = {} // Prebid adapter referrence doc: https://docs.prebid.org/dev-docs/bidder-adaptor.html +// Validity checks for optionsl paramters +const validParameter = { + url: (value) => typeof value === 'string', + placementId: (value) => { + const isString = typeof value === 'string' + const isNumber = typeof value === 'number' + return isString || isNumber + }, +} + export const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: ['ntv'], // short code supportedMediaTypes: SUPPORTED_AD_TYPES, @@ -26,7 +40,23 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return bid.params && !!bid.params.placementId + // We don't need any specific parameters to make a bid request + // If not parameters are supplied just verify it's the correct bidder code + if (!bid.params) return bid.bidder === BIDDER_CODE + + // Check if any supplied parameters are invalid + const hasInvalidParameters = Object.keys(bid.params).some(key => { + const value = bid.params[key] + const validityCheck = validParameter[key] + + // We don't have a test for this so it's not a paramter we care about + if (!validityCheck) return false + + // Return if the check is not passed + return !validityCheck(value) + }) + + return !hasInvalidParameters }, /** @@ -38,31 +68,69 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - const placementIds = [] + const placementIds = new Set() const placmentBidIdMap = {} let placementId, pageUrl validBidRequests.forEach((request) => { - pageUrl = pageUrl || request.params.url // Use the first url value found - placementId = request.params.placementId - placementIds.push(placementId) - placmentBidIdMap[placementId] = { + pageUrl = deepAccess( + request, + 'params.url', + bidderRequest.refererInfo.referer + ) + placementId = deepAccess(request, 'params.placementId') + + if (placementId) { + placementIds.add(placementId) + } + + var key = placementId || request.adUnitCode + placmentBidIdMap[key] = { bidId: request.bidId, size: getLargestSize(request.sizes), } }) bidRequestMap[bidderRequest.bidderRequestId] = placmentBidIdMap - if (!pageUrl) pageUrl = bidderRequest.refererInfo.referer + // Build adUnit data + const adUnitData = { + adUnits: validBidRequests.map((adUnit) => { + // Track if we've already requested for this ad unit code + adUnitsRequested[adUnit.adUnitCode] = + adUnitsRequested[adUnit.adUnitCode] !== undefined + ? adUnitsRequested[adUnit.adUnitCode] + 1 + : 0 + return { + adUnitCode: adUnit.adUnitCode, + mediaTypes: adUnit.mediaTypes, + } + }), + } + // Build QS Params let params = [ - { key: 'ntv_ptd', value: placementIds.toString() }, { key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId }, + { + key: 'ntv_ppc', + value: btoa(JSON.stringify(adUnitData)), // Convert to Base 64 + }, + { + key: 'ntv_dbr', + value: btoa(JSON.stringify(adUnitsRequested)), + }, { key: 'ntv_url', value: encodeURIComponent(pageUrl), }, ] + if (placementIds.size > 0) { + // Convert Set to Array (IE 11 Safe) + const placements = [] + placementIds.forEach((value) => placements.push(value)) + // Append to query string paramters + params.unshift({ key: 'ntv_ptd', value: placements.join(',') }) + } + if (bidderRequest.gdprConsent) { // Put on the beginning of the qs param array params.unshift({ @@ -95,7 +163,7 @@ export const spec = { */ interpretResponse: function (response, request) { // If the bid response was empty, return [] - if (!response || !response.body || utils.isEmpty(response.body)) return [] + if (!response || !response.body || isEmpty(response.body)) return [] try { const body = @@ -110,7 +178,7 @@ export const spec = { let bidResponse, adUnit seatbids.forEach((seatbid) => { seatbid.bid.forEach((bid) => { - adUnit = this.getRequestId(body.id, bid.impid) + adUnit = this.getAdUnitData(body.id, bid) bidResponse = { requestId: adUnit.bidId, cpm: bid.price, @@ -191,7 +259,7 @@ export const spec = { let body serverResponses.forEach((response) => { // If the bid response was empty, return [] - if (!response || !response.body || utils.isEmpty(response.body)) { + if (!response || !response.body || isEmpty(response.body)) { return syncs } @@ -243,14 +311,16 @@ export const spec = { /** * Maps Prebid's bidId to Nativo's placementId values per unique bidderRequestId * @param {String} bidderRequestId - The unique ID value associated with the bidderRequest - * @param {String} placementId - The placement ID value from Nativo + * @param {Object} bid - The placement ID value from Nativo * @returns {String} - The bidId value associated with the corresponding placementId */ - getRequestId: function (bidderRequestId, placementId) { - return ( - bidRequestMap[bidderRequestId] && - bidRequestMap[bidderRequestId][placementId] - ) + getAdUnitData: function (bidderRequestId, bid) { + var data = deepAccess(bidRequestMap, `${bidderRequestId}.${bid.impid}`) + + if (data) return data + + var unitCode = deepAccess(bid, 'ext.ad_unit_id') + return deepAccess(bidRequestMap, `${bidderRequestId}.${unitCode}`) }, } registerBidder(spec) diff --git a/modules/nativoBidAdapter.md b/modules/nativoBidAdapter.md index ec0980aae50..f83fb45b52e 100644 --- a/modules/nativoBidAdapter.md +++ b/modules/nativoBidAdapter.md @@ -29,7 +29,6 @@ var adUnits = [ bids: [{ bidder: 'nativo', params: { - placementId: 1125609, url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html' } }] diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js new file mode 100644 index 00000000000..7bd86879e9d --- /dev/null +++ b/modules/naveggIdSystem.js @@ -0,0 +1,90 @@ +/** + * This module adds naveggId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/naveggId + * @requires module:modules/userId + */ +import { isStr, isPlainObject, logError } from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'naveggId'; +const OLD_NAVEGG_ID = 'nid'; +const NAVEGG_ID = 'nvggid' + +export const storage = getStorageManager(); + +function readnaveggIdFromLocalStorage() { + return storage.getDataFromLocalStorage(NAVEGG_ID); +} + +function readnaveggIDFromCookie() { + return storage.cookiesAreEnabled ? storage.getCookie(NAVEGG_ID) : null; +} + +function readoldnaveggIDFromCookie() { + return storage.cookiesAreEnabled ? storage.getCookie(OLD_NAVEGG_ID) : null; +} + +function readnvgIDFromCookie() { + return storage.cookiesAreEnabled ? (storage.findSimilarCookies('nvg') ? storage.findSimilarCookies('nvg')[0] : null) : null; +} + +function readnavIDFromCookie() { + return storage.cookiesAreEnabled ? (storage.findSimilarCookies('nav') ? storage.findSimilarCookies('nav')[0] : null) : null; +} + +function readnvgnavFromLocalStorage() { + var i; + const query = '^nvg|^nav'; + for (i in window.localStorage) { + if (i.match(query) || (!query && typeof i === 'string')) { + return storage.getDataFromLocalStorage(i.match(query).input); + } + } +} + +/** @type {Submodule} */ +export const naveggIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param { Object | string | undefined } value + * @return { Object | string | undefined } + */ + decode(value) { + const naveggIdVal = value ? isStr(value) ? value : isPlainObject(value) ? value.id : undefined : undefined; + return naveggIdVal ? { + 'naveggId': naveggIdVal + } : undefined; + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} config + * @return {{id: string | undefined } | undefined} + */ + getId() { + let naveggIdStringFromLocalStorage = null; + if (storage.localStorageIsEnabled) { + naveggIdStringFromLocalStorage = readnaveggIdFromLocalStorage() || readnvgnavFromLocalStorage(); + } + + const naveggIdString = naveggIdStringFromLocalStorage || readnaveggIDFromCookie() || readoldnaveggIDFromCookie() || readnvgIDFromCookie() || readnavIDFromCookie(); + + if (typeof naveggIdString == 'string' && naveggIdString) { + try { + return { id: naveggIdString }; + } catch (error) { + logError(error); + } + } + return undefined; + } +}; +submodule('userId', naveggIdSubmodule); diff --git a/modules/naveggIdSystem.md b/modules/naveggIdSystem.md new file mode 100644 index 00000000000..f758fbc9d5d --- /dev/null +++ b/modules/naveggIdSystem.md @@ -0,0 +1,22 @@ +## Navegg User ID Submodule + +For assistance setting up your module please contact us at [prebid@navegg.com](prebid@navegg.com). + +### Prebid Params + +Individual params may be set for the IDx Submodule. +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'naveggId', + }] + } +}); +``` +## Parameter Descriptions for the `userSync` Configuration Section +The below parameters apply only to the naveggID integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID of the module - `"naveggId"` | `"naveggId"` | diff --git a/modules/newborntownWebBidAdapter.js b/modules/newborntownWebBidAdapter.js deleted file mode 100644 index 56c63e2bb4d..00000000000 --- a/modules/newborntownWebBidAdapter.js +++ /dev/null @@ -1,159 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, NATIVE} from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const storage = getStorageManager(); -const BIDDER_CODE = 'newborntownWeb'; - -const REQUEST_URL = 'https://us-west.solortb.com/adx/api/rtb?from=4' - -function randomn(n) { - return parseInt((Math.random() + 1) * Math.pow(10, n - 1)) + ''; -} -function generateGUID() { - var d = new Date().getTime(); - var guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - var r = (d + Math.random() * 16) % 16 | 0; - d = Math.floor(d / 16); - return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16); - }) - return guid; -} -function _isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); -} -function _isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); -} -function _getDeviceType() { - return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; -} -var platform = (function getPlatform() { - var ua = navigator.userAgent; - if (ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1) { - return 'Android' - } - if (ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) { - return 'iOS' - } - return 'windows' -})(); -function getLanguage() { - const language = navigator.language ? 'language' : 'userLanguage'; - return navigator[language].split('-')[0]; -} -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, NATIVE], - isBidRequestValid: function(bid) { - return !!(bid.params.publisher_id && bid.params.slot_id && bid.params.bidfloor); - }, - - buildRequests: function(validBidRequests, bidderRequest) { - let requestArr = [] - if (validBidRequests.length === 0) { - return null; - } - var guid; - if (storage.getDataFromLocalStorage('sax_user_id') == null) { - storage.setDataInLocalStorage('sax_user_id', generateGUID()) - } - guid = storage.getDataFromLocalStorage('sax_user_id') - utils._each(validBidRequests, function(bidRequest) { - const bidRequestObj = bidRequest.params - var req = { - id: randomn(12) + randomn(12), - tmax: bidderRequest.timeout, - bidId: bidRequest.bidId, - user: { - id: guid - }, - imp: [ - { - id: '1', - bidfloor: bidRequestObj.bidfloor, - bidfloorcur: 'USD', - banner: { - w: 0, - h: 0 - } - } - ], - site: { - domain: window.location.host, - id: bidRequestObj.slot_id, - page: window.location.href, - publisher: { - id: bidRequestObj.publisher_id - }, - }, - device: { - ip: '', - ua: navigator.userAgent, - os: platform, - geo: { - country: '', - type: 0, - ipservice: 1, - region: '', - city: '', - }, - language: getLanguage(), - devicetype: _getDeviceType() - }, - ext: { - solomath: { - slotid: bidRequestObj.slot_id - } - } - }; - var sizes = bidRequest.sizes; - if (sizes) { - if (sizes && utils.isArray(sizes[0])) { - req.imp[0].banner.w = sizes[0][0]; - req.imp[0].banner.h = sizes[0][1]; - } else if (sizes && utils.isNumber(sizes[0])) { - req.imp[0].banner.w = sizes[0]; - req.imp[0].banner.h = sizes[1]; - } - } else { - return false; - } - const options = { - withCredentials: false - } - requestArr.push({ - method: 'POST', - url: REQUEST_URL, - data: req, - bidderRequest, - options: options - }) - }) - return requestArr; - }, - interpretResponse: function(serverResponse, request) { - var bidResponses = []; - if (serverResponse.body.seatbid && serverResponse.body.seatbid.length > 0 && serverResponse.body.seatbid[0].bid && serverResponse.body.seatbid[0].bid.length > 0 && serverResponse.body.seatbid[0].bid[0].adm) { - utils._each(serverResponse.body.seatbid[0].bid, function(bodyAds) { - var adstr = ''; - adstr = bodyAds.adm; - var bidResponse = { - requestId: request.data.bidId || 0, - cpm: bodyAds.price || 0, - width: bodyAds.w ? bodyAds.w : 0, - height: bodyAds.h ? bodyAds.h : 0, - ad: adstr, - netRevenue: true, - currency: serverResponse.body.cur || 'USD', - ttl: 600, - creativeId: bodyAds.cid - }; - bidResponses.push(bidResponse); - }); - } - return bidResponses; - } -} -registerBidder(spec); diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index af1f0562ba4..85537d382c2 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,10 +1,10 @@ -import * as utils from '../src/utils.js'; +import { isStr, _each, getBidIdParameter } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'nextMillennium'; -const HOST = 'https://brainlyads.com'; -const CURRENCY = 'USD'; +const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; +const SYNC_ENDPOINT = 'https://statics.nextmillmedia.com/load-cookie.html?v=4'; const TIME_TO_LIVE = 360; export const spec = { @@ -13,24 +13,59 @@ export const spec = { isBidRequestValid: function(bid) { return !!( - bid.params.placement_id && utils.isNumber(bid.params.placement_id) + bid.params.placement_id && isStr(bid.params.placement_id) ); }, - buildRequests: function(validBidRequests) { - let requests = []; + buildRequests: function(validBidRequests, bidderRequest) { + const requests = []; + window.nmmRefreshCounts = window.nmmRefreshCounts || {}; + + _each(validBidRequests, function(bid) { + window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0; + const postBody = { + 'id': bid.auctionId, + 'ext': { + 'prebid': { + 'storedrequest': { + 'id': getBidIdParameter('placement_id', bid.params) + } + }, + 'nextMillennium': { + 'refresh_count': window.nmmRefreshCounts[bid.adUnitCode]++, + } + } + } + + const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + const uspConsent = bidderRequest && bidderRequest.uspConsent + + if (gdprConsent || uspConsent) { + postBody.regs = { ext: {} } + + if (uspConsent) { + postBody.regs.ext.us_privacy = uspConsent; + } + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies !== 'undefined') { + postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; + } + if (typeof gdprConsent.consentString !== 'undefined') { + postBody.user = { + ext: { consent: gdprConsent.consentString } + } + } + } + } - utils._each(validBidRequests, function(bid) { requests.push({ method: 'POST', - url: HOST + '/hb/s2s', + url: ENDPOINT, + data: JSON.stringify(postBody), options: { contentType: 'application/json', withCredentials: true }, - data: JSON.stringify({ - placement_id: utils.getBidIdParameter('placement_id', bid.params) - }), bidId: bid.bidId }); }); @@ -39,47 +74,55 @@ export const spec = { }, interpretResponse: function(serverResponse, bidRequest) { - try { - const bidResponse = serverResponse.body; - const bidResponses = []; + const response = serverResponse.body; + const bidResponses = []; - if (Number(bidResponse.cpm) > 0) { + _each(response.seatbid, (resp) => { + _each(resp.bid, (bid) => { bidResponses.push({ requestId: bidRequest.bidId, - cpm: bidResponse.cpm, - width: bidResponse.width, - height: bidResponse.height, - creativeId: bidResponse.creativeId, - currency: CURRENCY, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.adid, + currency: response.cur, netRevenue: false, ttl: TIME_TO_LIVE, - ad: bidResponse.ad + meta: { + advertiserDomains: bid.adomain || [] + }, + ad: bid.adm }); - } + }); + }); - return bidResponses; - } catch (err) { - utils.logError(err); - return []; - } + return bidResponses; }, - getUserSyncs: function(syncOptions) { - const syncs = [] - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: HOST + '/hb/s2s/matching' - }); + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + if (!syncOptions.iframeEnabled) { + return } - if (syncOptions.pixelEnabled) { - syncs.push({ - type: 'image', - url: HOST + '/hb/s2s/matching' - }); + let syncurl = gdprConsent && gdprConsent.gdprApplies ? `${SYNC_ENDPOINT}&gdpr=1&gdpr_consent=${gdprConsent.consentString}` : SYNC_ENDPOINT + + let bidders = [] + if (responses) { + _each(responses, (response) => { + if (!(response && response.body && response.body.ext && response.body.ext.responsetimemillis)) return + _each(Object.keys(response.body.ext.responsetimemillis), b => bidders.push(b)) + }) } - return syncs; - } + + if (bidders.length) { + syncurl += `&bidders=${bidders.join(',')}` + } + + return [{ + type: 'iframe', + url: syncurl + }]; + }, }; + registerBidder(spec); diff --git a/modules/nextMillenniumBidAdapter.md b/modules/nextMillenniumBidAdapter.md index c583969b4af..048fe907ac7 100644 --- a/modules/nextMillenniumBidAdapter.md +++ b/modules/nextMillenniumBidAdapter.md @@ -2,7 +2,7 @@ ``` Module Name: NextMillennium Bid Adapter Module Type: Bidder Adapter -Maintainer: mikhail.ivanchenko@iageengineering.net +Maintainer: mihail.ivanchenko@nextmillennium.io ``` # Description @@ -21,7 +21,7 @@ Currently module supports only banner mediaType. bids: [{ bidder: 'nextMillennium', params: { - placement_id: -1 + placement_id: '-1' } }] }]; diff --git a/modules/nextrollBidAdapter.js b/modules/nextrollBidAdapter.js index cb317190bea..b5af7ec1486 100644 --- a/modules/nextrollBidAdapter.js +++ b/modules/nextrollBidAdapter.js @@ -1,4 +1,14 @@ -import * as utils from '../src/utils.js'; +import { + deepAccess, + parseUrl, + isNumber, + getBidIdParameter, + isPlainObject, + isFn, + isStr, + replaceAuctionPrice, + isArray, +} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; @@ -29,7 +39,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - let topLocation = utils.parseUrl(utils.deepAccess(bidderRequest, 'refererInfo.referer')); + let topLocation = parseUrl(deepAccess(bidderRequest, 'refererInfo.referer')); return validBidRequests.map((bidRequest) => { return { @@ -42,12 +52,12 @@ export const spec = { id: bidRequest.bidId, imp: { id: bidRequest.bidId, - bidfloor: utils.getBidIdParameter('bidfloor', bidRequest.params), + bidfloor: _getFloor(bidRequest), banner: _getBanner(bidRequest), - native: _getNative(utils.deepAccess(bidRequest, 'mediaTypes.native')), + native: _getNative(deepAccess(bidRequest, 'mediaTypes.native')), ext: { zone: { - id: utils.getBidIdParameter('zoneId', bidRequest.params) + id: getBidIdParameter('zoneId', bidRequest.params) }, nextroll: { adapter_version: ADAPTER_VERSION @@ -139,13 +149,13 @@ function _getTitleAsset(title, _assetMap) { } function _getMinAspectRatio(aspectRatio, property) { - if (!utils.isPlainObject(aspectRatio)) return 1; + if (!isPlainObject(aspectRatio)) return 1; const ratio = aspectRatio['ratio_' + property]; const min = aspectRatio['min_' + property]; - if (utils.isNumber(ratio)) return ratio; - if (utils.isNumber(min)) return min; + if (isNumber(ratio)) return ratio; + if (isNumber(min)) return min; return 1; } @@ -177,7 +187,7 @@ function _getNativeAssets(mediaTypeNative) { } function _getUser(requests) { - const id = utils.deepAccess(requests, '0.userId.nextrollId'); + const id = deepAccess(requests, '0.userId.nextrollId'); if (id === undefined) { return; } @@ -192,6 +202,23 @@ function _getUser(requests) { }; } +function _getFloor(bidRequest) { + if (!isFn(bidRequest.getFloor)) { + return (bidRequest.params.bidfloor) ? bidRequest.params.bidfloor : null; + } + + let floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + function _buildResponse(bidResponse, bid) { let response = { requestId: bidResponse.id, @@ -202,11 +229,14 @@ function _buildResponse(bidResponse, bid) { dealId: bidResponse.dealId, currency: 'USD', netRevenue: true, - ttl: 300 + ttl: 300, + meta: { + advertiserDomains: bidResponse.adomain || [] + } }; - if (utils.isStr(bid.adm)) { + if (isStr(bid.adm)) { response.mediaType = BANNER; - response.ad = utils.replaceAuctionPrice(bid.adm, bid.price); + response.ad = replaceAuctionPrice(bid.adm, bid.price); } else { response.mediaType = NATIVE; response.native = _getNativeResponse(bid.adm, bid.price); @@ -221,8 +251,8 @@ function _getNativeResponse(adm, price) { let baseResponse = { clickTrackers: (adm.link && adm.link.clicktrackers) || [], jstracker: adm.jstracker || [], - clickUrl: utils.replaceAuctionPrice(adm.link.url, price), - impressionTrackers: adm.imptrackers.map(impTracker => utils.replaceAuctionPrice(impTracker, price)), + clickUrl: replaceAuctionPrice(adm.link.url, price), + impressionTrackers: adm.imptrackers.map(impTracker => replaceAuctionPrice(impTracker, price)), privacyLink: privacyLink, privacyIcon: privacyIcon }; @@ -257,19 +287,19 @@ function _getSite(bidRequest, topLocation) { page: topLocation.href, domain: topLocation.hostname, publisher: { - id: utils.getBidIdParameter('publisherId', bidRequest.params) + id: getBidIdParameter('publisherId', bidRequest.params) } }; } function _getSeller(bidRequest) { return { - id: utils.getBidIdParameter('sellerId', bidRequest.params) + id: getBidIdParameter('sellerId', bidRequest.params) }; } function _getSizes(bidRequest) { - if (!utils.isArray(bidRequest.sizes)) { + if (!isArray(bidRequest.sizes)) { return undefined; } return bidRequest.sizes.filter(_isValidSize).map(size => { diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js new file mode 100644 index 00000000000..a59bb635875 --- /dev/null +++ b/modules/nexx360BidAdapter.js @@ -0,0 +1,149 @@ +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'nexx360'; +const BIDDER_URL = 'https://fast.nexx360.io/prebid' +const CACHE_URL = 'https://fast.nexx360.io/cache' +const METRICS_TRACKER_URL = 'https://fast.nexx360.io/track-imp' + +export const spec = { + code: BIDDER_CODE, + aliases: ['revenuemaker'], // short code + supportedMediaTypes: [BANNER], + /** + * 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) { + return !!(bid.params.account && bid.params.tagId); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const adUnits = []; + const test = config.getConfig('debug') ? 1 : 0; + let adunitValue = null; + let userEids = null; + Object.keys(validBidRequests).forEach(key => { + adunitValue = validBidRequests[key]; + adUnits.push({ + account: adunitValue.params.account, + tagId: adunitValue.params.tagId, + label: adunitValue.adUnitCode, + bidId: adunitValue.bidId, + auctionId: adunitValue.auctionId, + transactionId: adunitValue.transactionId, + mediatypes: adunitValue.mediaTypes + }); + if (adunitValue.userIdAsEids) userEids = adunitValue.userIdAsEids; + }); + const payload = { + adUnits, + href: encodeURIComponent(bidderRequest.refererInfo.referer) + }; + if (bidderRequest) { // modules informations (gdpr, ccpa, schain, userId) + if (bidderRequest.gdprConsent) { + payload.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + payload.gdprConsent = bidderRequest.gdprConsent.consentString; + } else { + payload.gdpr = 0; + payload.gdprConsent = ''; + } + if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } + if (bidderRequest.schain) { payload.schain = bidderRequest.schain; } + if (userEids !== null) payload.userEids = userEids; + }; + if (test) payload.test = 1; + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: BIDDER_URL, + data: payloadString, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + const serverBody = serverResponse.body; + // const headerValue = serverResponse.headers.get('some-response-header'); + const bidResponses = []; + let bidResponse = null; + let value = null; + if (serverBody.hasOwnProperty('responses')) { + Object.keys(serverBody['responses']).forEach(key => { + value = serverBody['responses'][key]; + bidResponse = { + requestId: value['bidId'], + cpm: value['cpm'], + currency: value['currency'], + width: value['width'], + height: value['height'], + adUrl: `${CACHE_URL}?uuid=${value['uuid']}`, + ttl: value['ttl'], + creativeId: value['creativeId'], + netRevenue: true, + nexx360: { + 'ssp': value['bidder'], + 'consent': value['consent'], + 'tagId': value['tagId'] + }, + /* + meta: { + 'advertiserDomains': value['adomain'] + } + */ + }; + 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. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (typeof serverResponses === 'object' && serverResponses != null && serverResponses.length > 0 && serverResponses[0].hasOwnProperty('body') && + serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') { + return serverResponses[0].body.cookies.slice(0, 5); + } else { + return []; + } + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + onBidWon: function(bid) { + // fires a pixel to confirm a winning bid + const params = { type: 'prebid' }; + if (bid.hasOwnProperty('nexx360')) { + if (bid.nexx360.hasOwnProperty('ssp')) params.ssp = bid.nexx360.ssp; + if (bid.nexx360.hasOwnProperty('tagId')) params.tag_id = bid.nexx360.tagId; + if (bid.nexx360.hasOwnProperty('consent')) params.consent = bid.nexx360.consent; + }; + params.price = bid.cpm; + const url = `${METRICS_TRACKER_URL}?${new URLSearchParams(params).toString()}`; + ajax(url, null, undefined, {method: 'GET', withCredentials: true}); + return true; + } + +} +registerBidder(spec); diff --git a/modules/nexx360BidAdapter.md b/modules/nexx360BidAdapter.md new file mode 100644 index 00000000000..882d83cb24e --- /dev/null +++ b/modules/nexx360BidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Nexx360 Bid Adapter +Module Type: Bidder Adapter +Maintainer: gabriel@nexx360.io +``` + +# Description + +Connects to Nexx360 network for bids. + +Nexx360 bid adapter supports Banner only for the time being. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'nexx360', + params: { + account: '1067', + tagId: 'luvxjvgn' + } + }] + }, +]; +``` diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 051202cab97..d10c1d0e430 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -1,19 +1,20 @@ -import * as utils from '../src/utils.js'; +import { logInfo, deepAccess, logWarn, isArray, getParameterByName } 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 { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); +const GVLID = 816; const BIDDER_CODE = 'nobid'; -window.nobidVersion = '1.2.9'; +const storage = getStorageManager(GVLID, BIDDER_CODE); +window.nobidVersion = '1.3.2'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; window.nobid.timeoutTotal = 0; window.nobid.bidWonTotal = 0; window.nobid.refreshCount = 0; function log(msg, obj) { - utils.logInfo('-NoBid- ' + msg, obj) + logInfo('-NoBid- ' + msg, obj) } function nobidSetCookie(cname, cvalue, hours) { var d = new Date(); @@ -28,7 +29,7 @@ function nobidHasPurpose1Consent(bidderRequest) { let result = true; if (bidderRequest && bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { - result = !!(utils.deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); + result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); } } return result; @@ -111,11 +112,11 @@ function nobidBuildRequests(bids, bidderRequest) { var height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); return `${width}x${height}`; } catch (e) { - utils.logWarn('Could not parse screen dimensions, error details:', e); + logWarn('Could not parse screen dimensions, error details:', e); } } var getEIDs = function(eids) { - if (utils.isArray(eids) && eids.length > 0) { + if (isArray(eids) && eids.length > 0) { let src = []; eids.forEach((eid) => { let ids = []; @@ -144,12 +145,14 @@ function nobidBuildRequests(bids, bidderRequest) { state['ref'] = document.referrer; state['gdpr'] = gdprConsent(bidderRequest); state['usp'] = uspConsent(bidderRequest); + state['pjbdr'] = (bidderRequest && bidderRequest.bidderCode) ? bidderRequest.bidderCode : 'nobid'; const sch = schain(bids); if (sch) state['schain'] = sch; const cop = coppa(); if (cop) state['coppa'] = cop; - const eids = getEIDs(utils.deepAccess(bids, '0.userIdAsEids')); + const eids = getEIDs(deepAccess(bids, '0.userIdAsEids')); if (eids && eids.length > 0) state['eids'] = eids; + if (config && config.getConfig('ortb2')) state['ortb2'] = config.getConfig('ortb2'); return state; } function newAdunit(adunitObject, adunits) { @@ -226,8 +229,8 @@ function nobidBuildRequests(bids, bidderRequest) { var placementId = bid.params['placementId']; var adType = 'banner'; - const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + const videoMediaType = deepAccess(bid, 'mediaTypes.video'); + const context = deepAccess(bid, 'mediaTypes.video.context'); if (bid.mediaType === VIDEO || (videoMediaType && (context === 'instream' || context === 'outstream'))) { adType = 'video'; } @@ -298,6 +301,9 @@ function nobidInterpretResponse(response, bidRequest) { if (bid.videoCacheKey) { bidResponse.videoCacheKey = bid.videoCacheKey; } + if (bid.meta) { + bidResponse.meta = bid.meta; + } bidResponses.push(bidResponse); } @@ -334,6 +340,10 @@ window.addEventListener('message', function (event) { }, false); export const spec = { code: BIDDER_CODE, + gvlid: GVLID, + aliases: [ + { code: 'duration', gvlid: 674 } + ], supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -354,7 +364,7 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { function resolveEndpoint() { var ret = 'https://ads.servenobid.com/'; - var env = (typeof utils.getParameterByName === 'function') && (utils.getParameterByName('nobid-env')); + var env = (typeof getParameterByName === 'function') && (getParameterByName('nobid-env')); if (!env) ret = 'https://ads.servenobid.com/'; else if (env == 'beta') ret = 'https://beta.servenobid.com/'; else if (env == 'dev') ret = '//localhost:8282/'; @@ -439,7 +449,7 @@ export const spec = { } return syncs; } else { - utils.logWarn('-NoBid- Please enable iframe based user sync.', syncOptions); + logWarn('-NoBid- Please enable iframe based user sync.', syncOptions); return []; } }, diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index fbfa6ca8abc..a2210090c0f 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -5,24 +5,25 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js'; +import { logInfo } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js'; /** @type {Submodule} */ export const novatiqIdSubmodule = { -/** -* used to link submodule with config -* @type {string} -*/ + /** + * used to link submodule with config + * @type {string} + */ name: 'novatiq', /** -* decode the stored id value for passing to bid requests -* @function -* @returns {novatiq: {snowflake: string}} -*/ + * decode the stored id value for passing to bid requests + * @function + * @returns {novatiq: {snowflake: string}} + */ decode(novatiqId, config) { let responseObj = { novatiq: { @@ -33,11 +34,11 @@ export const novatiqIdSubmodule = { }, /** -* performs action to obtain id and return a value in the callback's response argument -* @function -* @param {SubmoduleConfig} config -* @returns {id: string} -*/ + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} config + * @returns {id: string} + */ getId(config) { function snowflakeId(placeholder) { return placeholder @@ -47,22 +48,74 @@ export const novatiqIdSubmodule = { const configParams = config.params || {}; const srcId = this.getSrcId(configParams); - utils.logInfo('NOVATIQ Sync request used sourceid param: ' + srcId); + const sharedId = this.getSharedId(configParams); + + logInfo('NOVATIQ config params: ' + JSON.stringify(configParams)); + logInfo('NOVATIQ Sync request used sourceid param: ' + srcId); + logInfo('NOVATIQ Sync request Shared ID: ' + sharedId); let partnerhost; partnerhost = window.location.hostname; - utils.logInfo('NOVATIQ partner hostname: ' + partnerhost); + logInfo('NOVATIQ partner hostname: ' + partnerhost); const novatiqId = snowflakeId(); - const url = 'https://spadsync.com/sync?sptoken=' + novatiqId + '&sspid=' + srcId + '&ssphost=' + partnerhost; + + let url = 'https://spadsync.com/sync?sptoken=' + novatiqId + '&sspid=' + srcId + '&ssphost=' + partnerhost; + + // for testing + let sharedStatus = 'Not Found'; + + // append on the shared ID if we have one + if (sharedId != null) { + url = url + '&sharedId=' + sharedId; + sharedStatus = 'Found'; + } + ajax(url, undefined, undefined, { method: 'GET', withCredentials: false }); - utils.logInfo('NOVATIQ snowflake: ' + novatiqId); - return { 'id': novatiqId } + logInfo('NOVATIQ snowflake: ' + novatiqId); + return { 'id': novatiqId, + 'sharedStatus': sharedStatus } + }, + + useSharedId(configParams) { + return typeof configParams.useSharedId != 'undefined' && configParams.useSharedId === true; + }, + + // return null if we aren't supposed to use one or we are but there isn't one present + getSharedId(configParams) { + let sharedId = null; + if (this.useSharedId(configParams)) { + let cookieOrStorageID = '_pubcid'; + + // Has the cookieOrStorageID been redefined? + if (typeof configParams.sharedIdName != 'undefined' && configParams.sharedIdName != null && configParams.sharedIdName != '') { + cookieOrStorageID = configParams.sharedIdName; + logInfo('NOVATIQ sharedID name redefined: ' + cookieOrStorageID); + } + + const storage = getStorageManager('', 'pubCommonId'); + + // first check local storage + if (storage.hasLocalStorage()) { + sharedId = storage.getDataFromLocalStorage(cookieOrStorageID); + logInfo('NOVATIQ sharedID retrieved from local storage:' + sharedId); + } + + // if nothing check the local cookies + if (sharedId == null) { + sharedId = storage.getCookie(cookieOrStorageID); + logInfo('NOVATIQ sharedID retrieved from cookies:' + sharedId); + } + } + + logInfo('NOVATIQ sharedID returning: ' + sharedId); + + return sharedId; }, getSrcId(configParams) { - utils.logInfo('NOVATIQ Configured sourceid param: ' + configParams.sourceid); + logInfo('NOVATIQ Configured sourceid param: ' + configParams.sourceid); function isHex(str) { var a = parseInt(str, 16); @@ -72,13 +125,13 @@ export const novatiqIdSubmodule = { let srcId; if (typeof configParams.sourceid === 'undefined' || configParams.sourceid === null || configParams.sourceid === '') { srcId = '000'; - utils.logInfo('NOVATIQ sourceid param set to value 000 due to undefined parameter or missing value in config section'); + logInfo('NOVATIQ sourceid param set to value 000 due to undefined parameter or missing value in config section'); } else if (configParams.sourceid.length < 3 || configParams.sourceid.length > 3) { srcId = '001'; - utils.logInfo('NOVATIQ sourceid param set to value 001 due to wrong size in config section 3 chars max e.g. 1ab'); + logInfo('NOVATIQ sourceid param set to value 001 due to wrong size in config section 3 chars max e.g. 1ab'); } else if (isHex(configParams.sourceid) == false) { srcId = '002'; - utils.logInfo('NOVATIQ sourceid param set to value 002 due to wrong format in config section expecting hex value only'); + logInfo('NOVATIQ sourceid param set to value 002 due to wrong format in config section expecting hex value only'); } else { srcId = configParams.sourceid; } diff --git a/modules/novatiqIdSystem.md b/modules/novatiqIdSystem.md index ce561a696e3..a6489594d19 100644 --- a/modules/novatiqIdSystem.md +++ b/modules/novatiqIdSystem.md @@ -1,8 +1,8 @@ -# Novatiq Snowflake ID +# Novatiq Hyper ID -Novatiq proprietary dynamic snowflake ID is a unique, non sequential and single use identifier for marketing activation. Our in network solution matches verification requests to telco network IDs, safely and securely inside telecom infrastructure. Novatiq snowflake ID can be used for identity validation and as a secured 1st party data delivery mechanism. +The Novatiq proprietary dynamic Hyper ID is a unique, non sequential and single use identifier for marketing activation. Our in network solution matches verification requests to telco network IDs safely and securely inside telecom infrastructure. The Novatiq Hyper ID can be used for identity validation and as a secured 1st party data delivery mechanism. -## Novatiq Snowflake ID Configuration +## Novatiq Hyper ID Configuration Enable by adding the Novatiq submodule to your Prebid.js package with: @@ -18,19 +18,81 @@ pbjs.setConfig({ userIds: [{ name: 'novatiq', params: { - sourceid '1a3', // change to the Partner Number you received from Novatiq + // change to the Partner Number you received from Novatiq + sourceid '1a3' } } }], - auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + // 50ms maximum auction delay, applies to all userId modules + auctionDelay: 50 } }); -``` +``` -| Param under userSync.userIds[] | Scope | Type | Description | Example | +### Parameters for the Novatiq Module +| Param | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String | Module identification: `"novatiq"` | `"novatiq"` | | params | Required | Object | Configuration specifications for the Novatiq module. | | | params.sourceid | Required | String | This is the Novatiq Partner Number obtained via Novatiq registration. | `1a3` | +# Novatiq Hyper ID with Prebid SharedID support +You can make use of the Prebid.js SharedId module as follows. + +## Novatiq Hyper ID Configuration + +Enable by adding the Novatiq and SharedId submodule to your Prebid.js package with: + +``` +gulp build --modules=novatiqIdSystem,userId,pubCommonId +``` + +Module activation and configuration: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [ + { + name: "pubCommonId", + storage: { + type: "cookie", + // optional: will default to _pubcid if left blank + name: "demo_pubcid", + + // expires in 1 years + expires: 365 + }, + bidders: [ 'adtarget' ] + }, + { + name: 'novatiq', + params: { + // change to the Partner Number you received from Novatiq + sourceid '1a3', + + // Use the sharedID module + useSharedId: true, + + // optional: will default to _pubcid if left blank. + // If not left blank must match "name" in the the module above + sharedIdName: 'demo_pubcid' + } + } + }], + // 50ms maximum auction delay, applies to all userId modules + auctionDelay: 50 + } +}); +``` + +### Parameters for the Novatiq Module +| Param | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | Module identification: `"novatiq"` | `"novatiq"` | +| params | Required | Object | Configuration specifications for the Novatiq module. | | +| params.sourceid | Required | String | The Novatiq Partner Number obtained via Novatiq | `1a3` | +| params.useSharedId | Optional | Boolean | Use the sharedID module if it's activated. | `true` | +| params.sharedIdName | Optional | String | Same as the SharedID "name" parameter
Defaults to "_pubcid" | `"demo_pubcid"` | + If you have any questions, please reach out to us at prebid@novatiq.com. diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js new file mode 100644 index 00000000000..7d2989b2066 --- /dev/null +++ b/modules/oguryBidAdapter.js @@ -0,0 +1,185 @@ +'use strict'; + +import { BANNER } from '../src/mediaTypes.js'; +import { getAdUnitSizes, logWarn, isFn, getWindowTop, getWindowSelf } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { ajax } from '../src/ajax.js' + +const BIDDER_CODE = 'ogury'; +const DEFAULT_TIMEOUT = 1000; +const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; +const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; +const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; +const ADAPTER_VERSION = '1.2.10'; + +function isBidRequestValid(bid) { + const adUnitSizes = getAdUnitSizes(bid); + + const isValidSizes = Boolean(adUnitSizes) && adUnitSizes.length > 0; + const isValidAdUnitId = !!bid.params.adUnitId; + const isValidAssetKey = !!bid.params.assetKey; + + return (isValidSizes && isValidAdUnitId && isValidAssetKey); +} + +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!syncOptions.pixelEnabled) return []; + + return [ + { + type: 'image', + url: `${MS_COOKIE_SYNC_DOMAIN}/v1/init-sync/bid-switch?iab_string=${(gdprConsent && gdprConsent.consentString) || ''}&source=prebid` + }, + { + type: 'image', + url: `${MS_COOKIE_SYNC_DOMAIN}/ttd/init-sync?iab_string=${(gdprConsent && gdprConsent.consentString) || ''}&source=prebid` + } + ] +} + +function buildRequests(validBidRequests, bidderRequest) { + const openRtbBidRequestBanner = { + id: bidderRequest.auctionId, + tmax: DEFAULT_TIMEOUT, + at: 1, + regs: { + ext: { + gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + }, + }, + site: { + domain: location.hostname, + page: location.href + }, + user: { + ext: { + consent: '' + } + }, + imp: [], + ext: { + adapterversion: ADAPTER_VERSION, + prebidversion: '$prebid.version$' + } + }; + + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) { + openRtbBidRequestBanner.user.ext.consent = bidderRequest.gdprConsent.consentString + } + + validBidRequests.forEach((bidRequest) => { + const sizes = getAdUnitSizes(bidRequest) + .map(size => ({ w: size[0], h: size[1] })); + + if (bidRequest.mediaTypes && + bidRequest.mediaTypes.hasOwnProperty('banner')) { + openRtbBidRequestBanner.site.id = bidRequest.params.assetKey; + + openRtbBidRequestBanner.imp.push({ + id: bidRequest.bidId, + tagid: bidRequest.params.adUnitId, + bidfloor: getFloor(bidRequest), + banner: { + format: sizes + }, + ext: bidRequest.params + }); + } + }); + + return { + method: 'POST', + url: BID_HOST, + data: openRtbBidRequestBanner, + options: {contentType: 'application/json'}, + }; +} + +function interpretResponse(openRtbBidResponse) { + if (!openRtbBidResponse || + !openRtbBidResponse.body || + typeof openRtbBidResponse.body != 'object' || + Object.keys(openRtbBidResponse.body).length === 0) { + logWarn('no response or body is malformed'); + return []; + } + + const bidResponses = []; + + openRtbBidResponse.body.seatbid.forEach((seatbid) => { + seatbid.bid.forEach((bid) => { + let bidResponse = { + requestId: bid.impid, + cpm: bid.price, + currency: 'USD', + width: bid.w, + height: bid.h, + creativeId: bid.id, + netRevenue: true, + ttl: 60, + ext: bid.ext, + meta: { + advertiserDomains: bid.adomain + }, + nurl: bid.nurl, + adapterVersion: ADAPTER_VERSION, + prebidVersion: '$prebid.version$' + }; + + bidResponse.ad = bid.adm; + + bidResponses.push(bidResponse); + }); + }); + return bidResponses; +} + +function getFloor(bid) { + if (!isFn(bid.getFloor)) { + return 0; + } + let floorResult = bid.getFloor({ + currency: 'USD', + mediaType: 'banner', + size: '*' + }); + return floorResult.currency === 'USD' ? floorResult.floor : 0; +} + +function getWindowContext() { + try { + return getWindowTop() + } catch (e) { + return getWindowSelf() + } +} + +function onBidWon(bid) { + const w = getWindowContext() + w.OG_PREBID_BID_OBJECT = { + ...(bid && { ...bid }), + } + if (bid && bid.nurl) ajax(bid.nurl, null); +} + +function onTimeout(timeoutData) { + ajax(`${TIMEOUT_MONITORING_HOST}/bid_timeout`, null, JSON.stringify({...timeoutData[0], location: window.location.href}), { + method: 'POST', + contentType: 'application/json' + }); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + getUserSyncs, + buildRequests, + interpretResponse, + getFloor, + onBidWon, + getWindowContext, + onTimeout +} + +registerBidder(spec); diff --git a/modules/oguryBidAdapter.md b/modules/oguryBidAdapter.md new file mode 100644 index 00000000000..7264602de14 --- /dev/null +++ b/modules/oguryBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Ogury Bidder Adapter +Module Type: Bidder Adapter +Maintainer: web.inventory@ogury.co +``` + +# Description + +Module that connects to Ogury's SSP solution +Ogury bid adapter supports Banner media type. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "ogury", + params: { + assetKey: 'OGY-CA41D116484F', + adUnitId: '2c4d61d0-90aa-0139-0cda-0242ac120004' + xMargin?: 20 + yMargin?: 20 + gravity?: 'TOP_LEFT' || 'TOP_RIGHT' || 'BOTTOM_LEFT' || 'BOTTOM_RIGHT' || 'BOTTOM_CENTER' || 'TOP_CENTER' || 'CENTER' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js index e57c9d12ff7..e0db143dc0f 100644 --- a/modules/oneVideoBidAdapter.js +++ b/modules/oneVideoBidAdapter.js @@ -1,10 +1,10 @@ -import * as utils from '../src/utils.js'; +import { logError, logWarn, parseSizesInput, generateUUID, isFn, logMessage, isPlainObject, isStr, isNumber, isArray } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'oneVideo'; export const spec = { code: 'oneVideo', - VERSION: '3.1.0', + VERSION: '3.1.2', ENDPOINT: 'https://ads.adaptv.advertising.com/rtb/openrtb?ext_id=', E2ETESTENDPOINT: 'https://ads-wc.v.ssp.yahoo.com/rtb/openrtb?ext_id=', SYNC_ENDPOINT1: 'https://pixel.advertising.com/ups/57304/sync?gdpr=&gdpr_consent=&_origin=0&redir=true', @@ -17,26 +17,46 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { + // Bidder code validation if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { return false; } - // Video validations - if (typeof bid.params.video === 'undefined' || typeof bid.params.video.playerWidth === 'undefined' || typeof bid.params.video.playerHeight == 'undefined' || typeof bid.params.video.mimes == 'undefined') { - return false; + // E2E test skip validations + if (bid.params && bid.params.video && bid.params.video.e2etest) { + return true; } + // MediaTypes Video / Banner validation + if (typeof bid.mediaTypes.video === 'undefined' && typeof bid.mediaTypes.banner === 'undefined') { + logError('Failed validation: adUnit mediaTypes.video OR mediaTypes.banner not declared'); + return false; + }; - // Prevend DAP Outstream validation, Banner DAP validation & Multi-Format adUnit support if (bid.mediaTypes.video) { - if (bid.mediaTypes.video.context === 'outstream' && bid.params.video.display === 1) { + // Player size validation + if (typeof bid.mediaTypes.video.playerSize === 'undefined') { + if (bid.params.video && (typeof bid.params.video.playerWidth === 'undefined' || typeof bid.params.video.playerHeight === 'undefined')) { + logError('Failed validation: Player size not declared in either mediaTypes.playerSize OR bid.params.video.plauerWidth & bid.params.video.playerHeight.'); + return false; + }; + }; + // Mimes validation + if (typeof bid.mediaTypes.video.mimes === 'undefined') { + if (!bid.params.video || typeof bid.params.video.mimes === 'undefined') { + logError('Failed validation: adUnit mediaTypes.mimes OR params.video.mimes not declared'); + return false; + }; + }; + // Prevend DAP Outstream validation, Banner DAP validation & Multi-Format adUnit support + if (bid.mediaTypes.video.context === 'outstream' && bid.params.video && bid.params.video.display === 1) { + logError('Failed validation: Dynamic Ad Placement cannot be used with context Outstream (params.video.display=1)'); return false; - } - } else if (bid.mediaTypes.banner && !bid.params.video.display) { - return false; - } + }; + }; - // Pub Id validation + // Publisher Id (Exchange) validation if (typeof bid.params.pubId === 'undefined') { + logError('Failed validation: Adapter cannot send requests without bid.params.pubId'); return false; } @@ -49,7 +69,7 @@ export const spec = { * @param bidderRequest * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(bids, bidRequest) { + buildRequests: function (bids, bidRequest) { let consentData = bidRequest ? bidRequest.gdprConsent : null; return bids.map(bid => { @@ -86,7 +106,7 @@ export const spec = { response = null; } if (!response || !bid || (!bid.adm && !bid.nurl) || !bid.price) { - utils.logWarn(`No valid bids from ${spec.code} bidder`); + logWarn(`No valid bids from ${spec.code} bidder`); return []; } size = getSize(bidRequest.sizes); @@ -100,7 +120,10 @@ export const spec = { currency: response.cur, ttl: (bidRequest.params.video.ttl > 0 && bidRequest.params.video.ttl <= 3600) ? bidRequest.params.video.ttl : 300, netRevenue: true, - adUnitCode: bidRequest.adUnitCode + adUnitCode: bidRequest.adUnitCode, + meta: { + advertiserDomains: bid.adomain + } }; bidResponse.mediaType = (bidRequest.mediaTypes.banner) ? 'banner' : 'video' @@ -149,7 +172,7 @@ export const spec = { }; function getSize(sizes) { - let parsedSizes = utils.parseSizesInput(sizes); + let parsedSizes = parseSizesInput(sizes); let [ width, height ] = parsedSizes.length ? parsedSizes[0].split('x') : []; return { width: parseInt(width, 10) || undefined, @@ -171,7 +194,7 @@ function getRequestData(bid, consentData, bidRequest) { size: '*' }; let bidData = { - id: utils.generateUUID(), + id: generateUUID(), at: 2, imp: [{ id: '1', @@ -194,40 +217,54 @@ function getRequestData(bid, consentData, bidRequest) { if (bid.params.video.display == undefined || bid.params.video.display != 1) { bidData.imp[0].video = { - mimes: bid.params.video.mimes, - w: bid.params.video.playerWidth, - h: bid.params.video.playerHeight, - pos: bid.params.video.position, + linearity: 1 + }; + if (bid.params.video.playerWidth && bid.params.video.playerHeight) { + bidData.imp[0].video.w = bid.params.video.playerWidth; + bidData.imp[0].video.h = bid.params.video.playerHeight; + } else { + const playerSize = getSize(bid.mediaTypes.video.playerSize); + bidData.imp[0].video.w = playerSize.width; + bidData.imp[0].video.h = playerSize.height; + }; + if (bid.params.video.mimes) { + bidData.imp[0].video.mimes = bid.params.video.mimes; + } else { + bidData.imp[0].video.mimes = bid.mediaTypes.video.mimes; }; - if (bid.params.video.maxbitrate) { - bidData.imp[0].video.maxbitrate = bid.params.video.maxbitrate + if (bid.mediaTypes.video.maxbitrate || bid.params.video.maxbitrate) { + bidData.imp[0].video.maxbitrate = bid.params.video.maxbitrate || bid.mediaTypes.video.maxbitrate; } - if (bid.params.video.maxduration) { - bidData.imp[0].video.maxduration = bid.params.video.maxduration + if (bid.mediaTypes.video.maxduration || bid.params.video.maxduration) { + bidData.imp[0].video.maxduration = bid.params.video.maxduration || bid.mediaTypes.video.maxduration; } - if (bid.params.video.minduration) { - bidData.imp[0].video.minduration = bid.params.video.minduration + if (bid.mediaTypes.video.minduration || bid.params.video.minduration) { + bidData.imp[0].video.minduration = bid.params.video.minduration || bid.mediaTypes.video.minduration; } - if (bid.params.video.api) { - bidData.imp[0].video.api = bid.params.video.api + if (bid.mediaTypes.video.api || bid.params.video.api) { + bidData.imp[0].video.api = bid.params.video.api || bid.mediaTypes.video.api; } - if (bid.params.video.delivery) { - bidData.imp[0].video.delivery = bid.params.video.delivery + if (bid.mediaTypes.video.delivery || bid.params.video.delivery) { + bidData.imp[0].video.delivery = bid.params.video.delivery || bid.mediaTypes.video.delivery; } - if (bid.params.video.position) { - bidData.imp[0].video.pos = bid.params.video.position + if (bid.mediaTypes.video.position || bid.params.video.position) { + bidData.imp[0].video.pos = bid.params.video.position || bid.mediaTypes.video.position; } - if (bid.params.video.playbackmethod) { - bidData.imp[0].video.playbackmethod = bid.params.video.playbackmethod + if (bid.mediaTypes.video.playbackmethod || bid.params.video.playbackmethod) { + bidData.imp[0].video.playbackmethod = bid.params.video.playbackmethod || bid.mediaTypes.video.playbackmethod; } - if (bid.params.video.placement) { - bidData.imp[0].video.placement = bid.params.video.placement + if (bid.mediaTypes.video.placement || bid.params.video.placement) { + bidData.imp[0].video.placement = bid.params.video.placement || bid.mediaTypes.video.placement; } if (bid.params.video.rewarded) { bidData.imp[0].ext.rewarded = bid.params.video.rewarded } - bidData.imp[0].video.linearity = 1; - bidData.imp[0].video.protocols = bid.params.video.protocols || [2, 5]; + if (bid.mediaTypes.video.linearity || bid.params.video.linearity) { + bidData.imp[0].video.linearity = bid.params.video.linearity || bid.mediaTypes.video.linearity || 1; + } + if (bid.mediaTypes.video.protocols || bid.params.video.protocols) { + bidData.imp[0].video.protocols = bid.params.video.protocols || bid.mediaTypes.video.protocols || [2, 5]; + } } else if (bid.params.video.display == 1) { getFloorRequestObject.mediaType = 'banner'; bidData.imp[0].banner = { @@ -249,7 +286,7 @@ function getRequestData(bid, consentData, bidRequest) { } } - if (utils.isFn(bid.getFloor)) { + if (isFn(bid.getFloor)) { let floorData = bid.getFloor(getFloorRequestObject); bidData.imp[0].bidfloor = floorData.floor; bidData.cur = floorData.currency; @@ -307,7 +344,7 @@ function getRequestData(bid, consentData, bidRequest) { } } if (bid.params.video.e2etest) { - utils.logMessage('+++ oneVideoBidAdapter: E2E test mode enabled. \n The following parameters are being overridden by e2etest mode:\n* bidfloor:null\n* width:300\n* height:250\n* mimes: video/mp4, application/javascript\n* api:2\n* site.page/ref: verizonmedia.com\n* tmax:1000'); + logMessage('E2E test mode enabled: \n The following parameters are being overridden by e2etest mode:\n* bidfloor:null\n* width:300\n* height:250\n* mimes: video/mp4, application/javascript\n* api:2\n* site.page/ref: verizonmedia.com\n* tmax:1000'); bidData.imp[0].bidfloor = null; bidData.imp[0].video.w = 300; bidData.imp[0].video.h = 250; @@ -317,15 +354,15 @@ function getRequestData(bid, consentData, bidRequest) { bidData.site.ref = 'https://verizonmedia.com'; bidData.tmax = 1000; } - if (bid.params.video.custom && utils.isPlainObject(bid.params.video.custom)) { + if (bid.params.video.custom && isPlainObject(bid.params.video.custom)) { bidData.imp[0].ext.custom = {}; for (const key in bid.params.video.custom) { - if (utils.isStr(bid.params.video.custom[key]) || utils.isNumber(bid.params.video.custom[key])) { + if (isStr(bid.params.video.custom[key]) || isNumber(bid.params.video.custom[key])) { bidData.imp[0].ext.custom[key] = bid.params.video.custom[key]; } } } - if (bid.params.video.content && utils.isPlainObject(bid.params.video.content)) { + if (bid.params.video.content && isPlainObject(bid.params.video.content)) { bidData.site.content = {}; const contentStringKeys = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language']; const contentNumberkeys = ['episode', 'prodq', 'context', 'livestream', 'len']; @@ -333,14 +370,14 @@ function getRequestData(bid, consentData, bidRequest) { const contentObjectKeys = ['ext']; for (const contentKey in bid.params.video.content) { if ( - (contentStringKeys.indexOf(contentKey) > -1 && utils.isStr(bid.params.video.content[contentKey])) || - (contentNumberkeys.indexOf(contentKey) > -1 && utils.isNumber(bid.params.video.content[contentKey])) || - (contentObjectKeys.indexOf(contentKey) > -1 && utils.isPlainObject(bid.params.video.content[contentKey])) || - (contentArrayKeys.indexOf(contentKey) > -1 && utils.isArray(bid.params.video.content[contentKey]) && - bid.params.video.content[contentKey].every(catStr => utils.isStr(catStr)))) { + (contentStringKeys.indexOf(contentKey) > -1 && isStr(bid.params.video.content[contentKey])) || + (contentNumberkeys.indexOf(contentKey) > -1 && isNumber(bid.params.video.content[contentKey])) || + (contentObjectKeys.indexOf(contentKey) > -1 && isPlainObject(bid.params.video.content[contentKey])) || + (contentArrayKeys.indexOf(contentKey) > -1 && isArray(bid.params.video.content[contentKey]) && + bid.params.video.content[contentKey].every(catStr => isStr(catStr)))) { bidData.site.content[contentKey] = bid.params.video.content[contentKey]; } else { - utils.logMessage('oneVideo bid adapter validation error: ', contentKey, ' is either not supported is OpenRTB V2.5 or value is undefined'); + logMessage('oneVideo bid adapter validation error: ', contentKey, ' is either not supported is OpenRTB V2.5 or value is undefined'); } } } diff --git a/modules/oneVideoBidAdapter.md b/modules/oneVideoBidAdapter.md index a42433e80b5..149a4b20e2f 100644 --- a/modules/oneVideoBidAdapter.md +++ b/modules/oneVideoBidAdapter.md @@ -3,12 +3,18 @@ **Module Name**: One Video Bidder Adapter **Module Type**: Bidder Adapter **Maintainer**: deepthi.neeladri.sravana@verizonmedia.com + adam.browning@verizonmedia.com # Description Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to fetch bids. - +# Prebid.js V5.0 Support +The oneVideo adapter now reads `mediaTypes.video` for mandatory parameters such as `playerSize` & `mimes`. +Note: You can use the `bid.params.video` object to specify explicit overrides for whatever is declared in `mediaTypes.video`. +Important: You must pass `bid.params.video = {}` as bare minimum for the adapter to work. # Integration Examples: -## Instream Video adUnit example & parameters + +## Instream Video adUnit using mediaTypes.video +*Note:* By default, the adapter will read the mandatory parameters from mediaTypes.video. *Note:* The Video SSP ad server will respond with an VAST XML to load into your defined player. ``` var adUnits = [ @@ -17,7 +23,18 @@ Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to mediaTypes: { video: { context: 'instream', - playerSize: [480, 640] + playerSize: [480, 640], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2], + minduration: 10, + maxduration: 30, + placement: 1, + playbackmethod: [1,5], + protocols: [2,5], + api: [2], } }, bids: [ @@ -25,21 +42,10 @@ Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to bidder: 'oneVideo', params: { video: { - playerWidth: 480, - playerHeight: 640, - mimes: ['video/mp4', 'application/javascript'], - protocols: [2,5], - api: [2], - position: 1, - delivery: [2], - playbackmethod: [1,5], sid: YOUR_VSSP_ORG_ID, hp: 1, rewarded: 1, - placement: 1, inventoryid: 123, - minduration: 10, - maxduration: 30, ttl: 300, custom: { key1: "value1", @@ -59,16 +65,16 @@ Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to } ] ``` -## Outstream Video adUnit example & parameters -*Note:* The Video SSP ad server will load it's own Outstream Renderer (player) as a fallback if no player is defined on the publisher page. The Outstream player will inject into the div id that has an identical adUnit code. +## Instream Video adUnit using params.video overrides +*Note:* If the mandatory parameters are not specified in mediaTypes.video the adapter will read check to see if overrides are set in params.video. Decalring values using params.video will always override the settings in mediaTypes.video. +*Note:* The Video SSP ad server will respond with an VAST XML to load into your defined player. ``` var adUnits = [ { code: 'video1', mediaTypes: { video: { - context: 'outstream', - playerSize: [480, 640] + context: 'instream', } }, bids: [ @@ -76,21 +82,74 @@ Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to bidder: 'oneVideo', params: { video: { - playerWidth: 480, - playerHeight: 640, + playerWidth: 640, + playerHeight: 480, mimes: ['video/mp4', 'application/javascript'], protocols: [2,5], api: [2], position: 1, delivery: [2], + minduration: 10, + maxduration: 30, + placement: 1, playbackmethod: [1,5], + protocols: [2,5], + api: [2], sid: YOUR_VSSP_ORG_ID, hp: 1, rewarded: 1, - placement: 1, inventoryid: 123, + ttl: 300, + custom: { + key1: "value1", + key2: 123345 + } + }, + bidfloor: 0.5, + site: { + id: 1, + page: 'https://verizonmedia.com', + referrer: 'https://verizonmedia.com' + }, + pubId: 'HBExchange' + } + } + ] + } + ] +``` +## Outstream Video adUnit example & parameters +*Note:* The Video SSP ad server will load it's own Outstream Renderer (player) as a fallback if no player is defined on the publisher page. The Outstream player will inject into the div id that has an identical adUnit code. +``` + var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [480, 640], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2], minduration: 10, maxduration: 30, + placement: 1, + playbackmethod: [1,5], + protocols: [2,5], + api: [2], + + } + }, + bids: [ + { + bidder: 'oneVideo', + params: { + video: { + sid: YOUR_VSSP_ORG_ID, + hp: 1, + rewarded: 1, ttl: 250 }, bidfloor: 0.5, @@ -116,7 +175,8 @@ Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to mediaTypes: { video: { context: "instream", - playerSize: [480, 640] + playerSize: [480, 640], + mimes: ['video/mp4', 'application/javascript'], } }, bids: [ @@ -124,11 +184,7 @@ Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to bidder: 'oneVideo', params: { video: { - playerWidth: 480, - playerHeight: 640, - mimes: ['video/mp4', 'application/javascript'], - position: 1, - display: 1 + ttl: 250 }, bidfloor: 0.5, site: { @@ -190,6 +246,7 @@ var adUnits = [ video: { context: "instream", playerSize: [480, 640] + mimes: ['video/mp4', 'application/javascript'], } }, bids: [ @@ -197,12 +254,8 @@ var adUnits = [ bidder: 'oneVideo', params: { video: { - playerWidth: 300, - playerHeight: 250, - mimes: ['video/mp4', 'application/javascript'], e2etest: true - }, - pubId: 'YOUR_PUBLISHER_ID' + } } } ] @@ -226,7 +279,10 @@ var adUnits = [ mediaTypes: { video: { context: 'instream', - playerSize: [480, 640] + playerSize: [480, 640], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], } }, bids: [ @@ -234,12 +290,7 @@ var adUnits = [ bidder: 'oneVideo', params: { video: { - playerWidth: 480, - playerHeight: 640, - mimes: ['video/mp4', 'application/javascript'], - protocols: [2,5], - api: [2], - sid: + sid: 123456 }, bidfloor: 0.5, site: { @@ -279,7 +330,10 @@ var adUnits = [ mediaTypes: { video: { context: 'instream', - playerSize: [480, 640] + playerSize: [480, 640], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], } }, bids: [ @@ -287,11 +341,7 @@ var adUnits = [ bidder: 'oneVideo', params: { video: { - playerWidth: 480, - playerHeight: 640, - mimes: ['video/mp4', 'application/javascript'], - protocols: [2,5], - api: [2], + ttl: 250 }, bidfloor: 0.5, site: { @@ -315,18 +365,16 @@ const adUnits = [{ mediaTypes: { video: { context: 'outstream', - playerSize: [640, 480] + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [1, 2], } }, bids: [{ bidder: 'oneVideo', params: { video: { - playerWidth: 640, - playerHeight: 480, - mimes: ['video/mp4', 'application/javascript'], - protocols: [2, 5], - api: [1, 2], ttl: 300, content: { id: "1234", @@ -373,18 +421,16 @@ const adUnits = [{ mediaTypes: { video: { context: 'outstream', - playerSize: [640, 480] + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [1, 2], } }, bids: [{ bidder: 'oneVideo', params: { video: { - playerWidth: 640, - playerHeight: 480, - mimes: ['video/mp4', 'application/javascript'], - protocols: [2, 5], - api: [1, 2], ttl: 300 }, bidfloor: 0.5, diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 16b8096646f..5642dce9018 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -7,6 +7,7 @@ import find from 'core-js-pure/features/array/find.js'; import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { createEidsArray } from './userId/eids.js'; +import { deepClone } from '../src/utils.js'; const ENDPOINT = 'https://onetag-sys.com/prebid-request'; const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; @@ -101,14 +102,18 @@ function interpretResponse(serverResponse, bidderRequest) { netRevenue: bid.netRevenue || false, mediaType: bid.mediaType, meta: { - mediaType: bid.mediaType + mediaType: bid.mediaType, + advertiserDomains: bid.adomain }, ttl: bid.ttl || 300 }; if (bid.mediaType === BANNER) { responseBid.ad = bid.ad; } else if (bid.mediaType === VIDEO) { - const {context, adUnitCode} = find(requestData.bids, (item) => item.bidId === bid.requestId); + const {context, adUnitCode} = find(requestData.bids, (item) => + item.bidId === bid.requestId && + item.type === VIDEO + ); if (context === INSTREAM) { responseBid.vastUrl = bid.vastUrl; responseBid.videoCacheKey = bid.videoCacheKey; @@ -200,6 +205,10 @@ function getPageInfo() { topmostFrame.document.referrer !== '' ? topmostFrame.document.referrer : null, + ancestorOrigin: + window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0 + ? window.location.ancestorOrigins[window.location.ancestorOrigins.length - 1] + : null, masked: currentFrameNesting, wWidth: topmostFrame.innerWidth, wHeight: topmostFrame.innerHeight, @@ -231,14 +240,10 @@ function requestsToBids(bidRequests) { // Pass parameters // Context: instream - outstream - adpod videoObj['context'] = bidRequest.mediaTypes.video.context; - // MIME Video Types - videoObj['mimes'] = bidRequest.mediaTypes.video.mimes; // Sizes videoObj['playerSize'] = parseVideoSize(bidRequest); // Other params - videoObj['protocols'] = bidRequest.mediaTypes.video.protocols; - videoObj['maxDuration'] = bidRequest.mediaTypes.video.maxduration; - videoObj['api'] = bidRequest.mediaTypes.video.api; + videoObj['mediaTypeInfo'] = deepClone(bidRequest.mediaTypes.video); videoObj['type'] = VIDEO; return videoObj; }); @@ -247,6 +252,7 @@ function requestsToBids(bidRequests) { setGeneralInfo.call(bannerObj, bidRequest); bannerObj['sizes'] = parseSizes(bidRequest); bannerObj['type'] = BANNER; + bannerObj['mediaTypeInfo'] = deepClone(bidRequest.mediaTypes.banner); return bannerObj; }); return videoBidRequests.concat(bannerBidRequests); @@ -341,11 +347,13 @@ function getSizes(sizes) { function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { let syncs = []; let params = ''; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - params += '&gdpr_consent=' + gdprConsent.consentString; + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { params += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); } + if (typeof gdprConsent.consentString === 'string') { + params += '&gdpr_consent=' + gdprConsent.consentString; + } } if (uspConsent && typeof uspConsent === 'string') { params += '&us_privacy=' + uspConsent; diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js index 55fca29fbf3..25b0f1a5934 100644 --- a/modules/onomagicBidAdapter.js +++ b/modules/onomagicBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { getBidIdParameter, _each, isArray, getWindowTop, getUniqueIdentifierStr, parseUrl, logError, logWarn, createTrackPixelHtml, getWindowSelf, isFn, isPlainObject } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -22,17 +22,17 @@ function buildRequests(bidReqs, bidderRequest) { referrer = bidderRequest.refererInfo.referer; } const onomagicImps = []; - const publisherId = utils.getBidIdParameter('publisherId', bidReqs[0].params); - utils._each(bidReqs, function (bid) { + const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); + _each(bidReqs, function (bid) { let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; - bidSizes = ((utils.isArray(bidSizes) && utils.isArray(bidSizes[0])) ? bidSizes : [bidSizes]); - bidSizes = bidSizes.filter(size => utils.isArray(size)); + bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => isArray(size)); const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); const element = document.getElementById(bid.adUnitCode); const minSize = _getMinSize(processedSizes); const viewabilityAmount = _isViewabilityMeasurable(element) - ? _getViewability(element, utils.getWindowTop(), minSize) + ? _getViewability(element, getWindowTop(), minSize) : 'na'; const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); @@ -46,17 +46,17 @@ function buildRequests(bidReqs, bidderRequest) { }, tagid: String(bid.adUnitCode) }; - const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); + const bidFloor = _getBidFloor(bid); if (bidFloor) { imp.bidfloor = bidFloor; } onomagicImps.push(imp); }); const onomagicBidReq = { - id: utils.getUniqueIdentifierStr(), + id: getUniqueIdentifierStr(), imp: onomagicImps, site: { - domain: utils.parseUrl(referrer).host, + domain: parseUrl(referrer).host, page: referrer, publisher: { id: publisherId @@ -77,7 +77,7 @@ function buildRequests(bidReqs, bidderRequest) { options: {contentType: 'text/plain', withCredentials: false} }; } catch (e) { - utils.logError(e, {bidReqs, bidderRequest}); + logError(e, {bidReqs, bidderRequest}); } } @@ -95,7 +95,7 @@ function isBidRequestValid(bid) { function interpretResponse(serverResponse) { if (!serverResponse.body || typeof serverResponse.body != 'object') { - utils.logWarn('Onomagic server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + logWarn('Onomagic server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); return []; } const { body: {id, seatbid} } = serverResponse; @@ -117,13 +117,16 @@ function interpretResponse(serverResponse) { netRevenue: true, mediaType: BANNER, ad: _getAdMarkup(onomagicBid), - ttl: 60 + ttl: 60, + meta: { + advertiserDomains: onomagicBid && onomagicBid.adomain ? onomagicBid.adomain : [] + } }); }); } return onomagicBidResponses; } catch (e) { - utils.logError(e, {id, seatbid}); + logError(e, {id, seatbid}); } } @@ -147,7 +150,7 @@ function _getDeviceType() { function _getAdMarkup(bid) { let adm = bid.adm; if ('nurl' in bid) { - adm += utils.createTrackPixelHtml(bid.nurl); + adm += createTrackPixelHtml(bid.nurl); } return adm; } @@ -157,14 +160,14 @@ function _isViewabilityMeasurable(element) { } function _getViewability(element, topWin, { w, h } = {}) { - return utils.getWindowTop().document.visibilityState === 'visible' + return getWindowTop().document.visibilityState === 'visible' ? _getPercentInView(element, topWin, { w, h }) : 0; } function _isIframe() { try { - return utils.getWindowSelf() !== utils.getWindowTop(); + return getWindowSelf() !== getWindowTop(); } catch (e) { return true; } @@ -243,4 +246,20 @@ function _getPercentInView(element, topWin, { w, h } = {}) { return 0; } +function _getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + registerBidder(spec); diff --git a/modules/ooloAnalyticsAdapter.js b/modules/ooloAnalyticsAdapter.js index 7195d6ac177..398459d604d 100644 --- a/modules/ooloAnalyticsAdapter.js +++ b/modules/ooloAnalyticsAdapter.js @@ -1,7 +1,7 @@ +import { _each, deepClone, pick, deepSetValue, getOrigin, logError, logInfo } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js' import adapterManager from '../src/adapterManager.js' import CONSTANTS from '../src/constants.json' -import * as utils from '../src/utils.js' import { ajax } from '../src/ajax.js' import { config } from '../src/config.js' @@ -64,7 +64,7 @@ const onAuctionInit = (args) => { handleCustomFields(auction, AUCTION_INIT, args) - utils._each(adUnits, adUnit => { + _each(adUnits, adUnit => { auction.adUnits[adUnit.code] = { ...adUnit, auctionId, @@ -144,7 +144,7 @@ const onBidWon = (args) => { } const onBidTimeout = (args) => { - utils._each(args, bid => { + _each(args, bid => { const { auctionId, adUnitCode } = bid const bidId = parseBidId(bid) let bidCache = auctions[auctionId].adUnits[adUnitCode].bids[bidId] @@ -172,7 +172,7 @@ const onAuctionEnd = (args) => { } const onAdRenderFailed = (args) => { - const data = utils.deepClone(args) + const data = deepClone(args) data.timestamp = Date.now() if (data.bid) { @@ -232,7 +232,7 @@ function handleEvent(eventType, args) { } function sendEvent(eventType, args, isRaw) { - let data = utils.deepClone(args) + let data = deepClone(args) Object.assign(data, buildCommonDataProperties(), { eventType @@ -268,7 +268,7 @@ function checkEventsQueue() { } function buildAuctionData(auction) { - const auctionData = utils.deepClone(auction) + const auctionData = deepClone(auction) const keysToRemove = ['adUnitCodes', 'auctionStatus', 'bidderRequests', 'bidsReceived', 'noBids', 'winningBids', 'timestamp', 'config'] keysToRemove.forEach(key => { @@ -367,12 +367,12 @@ function handleCustomFields(obj, eventType, args) { const { pickFields, omitFields } = initOptions.serverConfig.events[eventType] if (pickFields && obj && args) { - Object.assign(obj, utils.pick(args, pickFields)) + Object.assign(obj, pick(args, pickFields)) } if (omitFields && obj && args) { omitFields.forEach(field => { - utils.deepSetValue(obj, field, undefined) + deepSetValue(obj, field, undefined) }) } } catch (e) { } @@ -382,7 +382,7 @@ function handleCustomRawFields(obj, omitRawFields) { try { if (omitRawFields && obj) { omitRawFields.forEach(field => { - utils.deepSetValue(obj, field, undefined) + deepSetValue(obj, field, undefined) }) } } catch (e) { } @@ -419,7 +419,7 @@ function sendPage() { screenHeight: window.screen.height, url: window.location.href, protocol: window.location.protocol, - origin: utils.getOrigin(), + origin: getOrigin(), referrer: getTopWindowReferrer(), pbVersion: prebidVersion, } @@ -507,7 +507,7 @@ ooloAdapter.enableAnalytics = function (config) { initOptions = config ? config.options : {} if (!initOptions.pid) { - utils.logError(buildLogMessage('enableAnalytics missing config object with "pid"')) + logError(buildLogMessage('enableAnalytics missing config object with "pid"')) return } @@ -520,9 +520,9 @@ ooloAdapter.enableAnalytics = function (config) { window.addEventListener('load', sendPage) } - utils.logInfo(buildLogMessage('enabled analytics adapter'), config) + logInfo(buildLogMessage('enabled analytics adapter'), config) ooloAdapter.enableAnalytics = function () { - utils.logInfo(buildLogMessage('Analytics adapter already enabled..')) + logInfo(buildLogMessage('Analytics adapter already enabled..')) } } diff --git a/modules/open8BidAdapter.js b/modules/open8BidAdapter.js deleted file mode 100644 index 744cce2b5f9..00000000000 --- a/modules/open8BidAdapter.js +++ /dev/null @@ -1,185 +0,0 @@ -import { Renderer } from '../src/Renderer.js'; -import {ajax} from '../src/ajax.js'; -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'open8'; -const URL = 'https://as.vt.open8.com/v1/control/prebid'; -const AD_TYPE = { - VIDEO: 1, - BANNER: 2 -}; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [VIDEO, BANNER], - - isBidRequestValid: function(bid) { - return !!(bid.params.slotKey); - }, - - buildRequests: function(validBidRequests, bidderRequest) { - var requests = []; - for (var i = 0; i < validBidRequests.length; i++) { - var bid = validBidRequests[i]; - var queryString = ''; - var slotKey = utils.getBidIdParameter('slotKey', bid.params); - queryString = utils.tryAppendQueryString(queryString, 'slot_key', slotKey); - queryString = utils.tryAppendQueryString(queryString, 'imp_id', generateImpId()); - queryString += ('bid_id=' + bid.bidId); - - requests.push({ - method: 'GET', - url: URL, - data: queryString - }); - } - return requests; - }, - - interpretResponse: function(serverResponse, request) { - var bidderResponse = serverResponse.body; - - if (!bidderResponse.isAdReturn) { - return []; - } - - var ad = bidderResponse.ad; - - const bid = { - slotKey: bidderResponse.slotKey, - userId: bidderResponse.userId, - impId: bidderResponse.impId, - media: bidderResponse.media, - ds: ad.ds, - spd: ad.spd, - fa: ad.fa, - pr: ad.pr, - mr: ad.mr, - nurl: ad.nurl, - requestId: ad.bidId, - cpm: ad.price, - creativeId: ad.creativeId, - dealId: ad.dealId, - currency: ad.currency || 'JPY', - netRevenue: true, - ttl: 360, // 6 minutes - } - - if (ad.adType === AD_TYPE.VIDEO) { - const videoAd = bidderResponse.ad.video; - Object.assign(bid, { - vastXml: videoAd.vastXml, - width: videoAd.w, - height: videoAd.h, - renderer: newRenderer(bidderResponse), - adResponse: bidderResponse, - mediaType: VIDEO - }); - } else if (ad.adType === AD_TYPE.BANNER) { - const bannerAd = bidderResponse.ad.banner; - Object.assign(bid, { - width: bannerAd.w, - height: bannerAd.h, - ad: bannerAd.adm, - mediaType: BANNER - }); - if (bannerAd.imps) { - try { - bannerAd.imps.forEach(impTrackUrl => { - const tracker = utils.createTrackPixelHtml(impTrackUrl); - bid.ad += tracker; - }); - } catch (error) { - utils.logError('Error appending imp tracking pixel', error); - } - } - } - return [bid]; - }, - - getUserSyncs: function(syncOptions, serverResponses) { - const syncs = []; - if (syncOptions.iframeEnabled && serverResponses.length) { - const syncIFs = serverResponses[0].body.syncIFs; - if (syncIFs) { - syncIFs.forEach(sync => { - syncs.push({ - type: 'iframe', - url: sync - }); - }); - } - } - if (syncOptions.pixelEnabled && serverResponses.length) { - const syncPixs = serverResponses[0].body.syncPixels; - if (syncPixs) { - syncPixs.forEach(sync => { - syncs.push({ - type: 'image', - url: sync - }); - }); - } - } - return syncs; - }, - onBidWon: function(bid) { - if (!bid.nurl) { return; } - const winUrl = bid.nurl.replace( - /\$\{AUCTION_PRICE\}/, - bid.cpm - ); - ajax(winUrl, null); - } -} - -function generateImpId() { - var l = 16; - var c = 'abcdefghijklmnopqrstuvwsyz0123456789'; - var cl = c.length; - var r = ''; - for (var i = 0; i < l; i++) { - r += c[Math.floor(Math.random() * cl)]; - } - return r; -} - -function newRenderer(bidderResponse) { - const renderer = Renderer.install({ - id: bidderResponse.ad.bidId, - url: bidderResponse.ad.video.purl, - loaded: false, - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - utils.logWarn('Prebid Error calling setRender on newRenderer', err); - } - - return renderer; -} - -function outstreamRender(bid) { - bid.renderer.push(() => { - window.op8.renderPrebid({ - vastXml: bid.vastXml, - adUnitCode: bid.adUnitCode, - slotKey: bid.slotKey, - impId: bid.impId, - userId: bid.userId, - media: bid.media, - ds: bid.ds, - spd: bid.spd, - fa: bid.fa, - pr: bid.pr, - mr: bid.mr, - adResponse: bid.adResponse, - mediaType: bid.mediaType - }); - }); -} - -registerBidder(spec); diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js new file mode 100644 index 00000000000..9476d2d2914 --- /dev/null +++ b/modules/openwebBidAdapter.js @@ -0,0 +1,247 @@ +import { isNumber, deepAccess, isArray, 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 find from 'core-js-pure/features/array/find.js'; + +const ENDPOINT = 'https://ghb.spotim.market/v2/auction'; +const BIDDER_CODE = 'openweb'; +const DISPLAY = 'display'; +const syncsCache = {}; + +export const spec = { + code: BIDDER_CODE, + gvlid: 280, + supportedMediaTypes: [VIDEO, BANNER, ADPOD], + isBidRequestValid: function (bid) { + return isNumber(deepAccess(bid, 'params.aid')); + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + + function addSyncs(bid) { + const uris = bid.cookieURLs; + const types = bid.cookieURLSTypes || []; + + if (Array.isArray(uris)) { + uris.forEach((uri, i) => { + const type = types[i] || 'image'; + + if ((!syncOptions.pixelEnabled && type === 'image') || + (!syncOptions.iframeEnabled && type === 'iframe') || + syncsCache[uri]) { + return; + } + + syncsCache[uri] = true; + syncs.push({ + type: type, + url: uri + }) + }) + } + } + + if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { + isArray(serverResponses) && serverResponses.forEach((response) => { + if (response.body) { + if (isArray(response.body)) { + response.body.forEach(b => { + addSyncs(b); + }) + } else { + addSyncs(response.body) + } + } + }) + } + return syncs; + }, + /** + * Make a server request from the list of BidRequests + * @param bidRequests + * @param adapterRequest + */ + buildRequests: function (bidRequests, adapterRequest) { + const { tag, bids } = bidToTag(bidRequests, adapterRequest); + return [{ + data: Object.assign({}, tag, { BidRequests: bids }), + adapterRequest, + method: 'POST', + url: ENDPOINT + }]; + }, + + /** + * Unpack the response from the server into a list of bids + * @param serverResponse + * @param bidderRequest + * @return {Bid[]} An array of bids which were nested inside the server + */ + interpretResponse: function (serverResponse, { adapterRequest }) { + serverResponse = serverResponse.body; + let bids = []; + + if (!isArray(serverResponse)) { + return parseRTBResponse(serverResponse, adapterRequest); + } + + serverResponse.forEach(serverBidResponse => { + bids = flatten(bids, parseRTBResponse(serverBidResponse, adapterRequest)); + }); + + return bids; + }, + + transformBidParams(params) { + return convertTypes({ + 'aid': 'number', + }, params); + } +}; + +function parseRTBResponse(serverResponse, adapterRequest) { + const isEmptyResponse = !serverResponse || !isArray(serverResponse.bids); + const bids = []; + + if (isEmptyResponse) { + return bids; + } + + serverResponse.bids.forEach(serverBid => { + const request = find(adapterRequest.bids, (bidRequest) => { + return bidRequest.bidId === serverBid.requestId; + }); + + if (serverBid.cpm !== 0 && request !== undefined) { + const bid = createBid(serverBid, request); + + bids.push(bid); + } + }); + + return bids; +} + +function bidToTag(bidRequests, adapterRequest) { + // start publisher env + const tag = { + Domain: deepAccess(adapterRequest, 'refererInfo.referer') + }; + if (config.getConfig('coppa') === true) { + tag.Coppa = 1; + } + if (deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) { + tag.GDPR = 1; + tag.GDPRConsent = deepAccess(adapterRequest, 'gdprConsent.consentString'); + } + if (deepAccess(adapterRequest, 'uspConsent')) { + tag.USP = deepAccess(adapterRequest, 'uspConsent'); + } + if (deepAccess(bidRequests[0], 'schain')) { + tag.Schain = deepAccess(bidRequests[0], 'schain'); + } + if (deepAccess(bidRequests[0], 'userId')) { + tag.UserIds = deepAccess(bidRequests[0], 'userId'); + } + if (deepAccess(bidRequests[0], 'userIdAsEids')) { + tag.UserEids = deepAccess(bidRequests[0], 'userIdAsEids'); + } + // end publisher env + const bids = [] + + for (let i = 0, length = bidRequests.length; i < length; i++) { + const bid = prepareBidRequests(bidRequests[i]); + bids.push(bid); + } + + return { tag, bids }; +} + +/** + * Parse mediaType + * @param bidReq {object} + * @returns {object} + */ +function prepareBidRequests(bidReq) { + const mediaType = deepAccess(bidReq, 'mediaTypes.video') ? VIDEO : DISPLAY; + const sizes = mediaType === VIDEO ? deepAccess(bidReq, 'mediaTypes.video.playerSize') : deepAccess(bidReq, 'mediaTypes.banner.sizes'); + const bidReqParams = { + 'CallbackId': bidReq.bidId, + 'Aid': bidReq.params.aid, + 'AdType': mediaType, + 'Sizes': parseSizesInput(sizes).join(',') + }; + + bidReqParams.PlacementId = bidReq.adUnitCode; + if (bidReq.params.iframe) { + bidReqParams.AdmType = 'iframe'; + } + if (mediaType === VIDEO) { + const context = deepAccess(bidReq, 'mediaTypes.video.context'); + if (context === ADPOD) { + bidReqParams.Adpod = deepAccess(bidReq, 'mediaTypes.video'); + } + } + return bidReqParams; +} + +/** + * Prepare all parameters for request + * @param bidderRequest {object} + * @returns {object} + */ +function getMediaType(bidderRequest) { + return deepAccess(bidderRequest, 'mediaTypes.video') ? VIDEO : BANNER; +} + +/** + * Configure new bid by response + * @param bidResponse {object} + * @param bidRequest {Object} + * @returns {object} + */ +function createBid(bidResponse, bidRequest) { + const mediaType = getMediaType(bidRequest) + const context = deepAccess(bidRequest, 'mediaTypes.video.context'); + const bid = { + requestId: bidResponse.requestId, + creativeId: bidResponse.cmpId, + height: bidResponse.height, + currency: bidResponse.cur, + width: bidResponse.width, + cpm: bidResponse.cpm, + netRevenue: true, + mediaType, + ttl: 300, + meta: { + advertiserDomains: bidResponse.adomain || [] + } + }; + + if (mediaType === BANNER) { + return Object.assign(bid, { + ad: bidResponse.ad, + adUrl: bidResponse.adUrl, + }); + } + if (context === ADPOD) { + Object.assign(bid, { + meta: { + primaryCatId: bidResponse.primaryCatId, + }, + video: { + context: ADPOD, + durationSeconds: bidResponse.durationSeconds + } + }); + } + + Object.assign(bid, { + vastUrl: bidResponse.vastUrl + }); + + return bid; +} + +registerBidder(spec); diff --git a/modules/openwebBidAdapter.md b/modules/openwebBidAdapter.md new file mode 100644 index 00000000000..dc8bfa6c59e --- /dev/null +++ b/modules/openwebBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +**Module Name**: OpenWeb Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: monetization@openweb.com + +# Description + +OpenWeb.com official prebid adapter. Available in both client and server side versions. +OpenWeb header bidding adapter provides solution for accessing both Video and Display demand. + +# Test Parameters +``` + var adUnits = [ + // Banner adUnit + { + code: 'div-test-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'openweb', + params: { + aid: 529814 + } + }] + } + ]; +``` diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 07966e9e401..f67f8bd0c75 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -1,10 +1,10 @@ +import { logInfo, logError, getWindowLocation, parseQS, logMessage, _each, deepAccess, logWarn, _map, flatten, uniques, isEmpty, parseSizesInput } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import { ajax } from '../src/ajax.js'; import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; -const utils = require('../src/utils.js'); export const AUCTION_STATES = { INIT: 'initialized', // auction has initialized @@ -46,9 +46,9 @@ const UTM_TO_CAMPAIGN_PROPERTIES = { * @property {string} orgId * @property {string} publisherPlatformId * @property {number} publisherAccountId + * @property {string} configId + * @property {string} optimizerConfig * @property {number} sampling - * @property {boolean} enableV2 - * @property {boolean} testPipeline * @property {Object} campaign * @property {number} payloadWaitTime * @property {number} payloadWaitTimePadding @@ -92,7 +92,7 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) { // campaign properties defined by config will override utm query parameters analyticsConfig.campaign = {...buildCampaignFromUtmCodes(), ...analyticsConfig.campaign}; - utils.logInfo('OpenX Analytics enabled with config', analyticsConfig); + logInfo('OpenX Analytics enabled with config', analyticsConfig); // override track method with v2 handlers openxAdapter.track = prebidAnalyticsEventHandler; @@ -103,7 +103,7 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) { if (pubads.addEventListener) { pubads.addEventListener(SLOT_LOADED, args => { openxAdapter.track({eventType: SLOT_LOADED, args}); - utils.logInfo('OX: SlotOnLoad event triggered'); + logInfo('OX: SlotOnLoad event triggered'); }); } }); @@ -141,9 +141,9 @@ function isValidConfig({options: analyticsOptions}) { ['orgId', 'string', hasOrgId], ['publisherPlatformId', 'string', !hasOrgId], ['publisherAccountId', 'number', !hasOrgId], + ['configId', 'string', false], + ['optimizerConfig', 'string', false], ['sampling', 'number', false], - ['enableV2', 'boolean', false], - ['testPipeline', 'boolean', false], ['adIdKey', 'string', false], ['payloadWaitTime', 'number', false], ['payloadWaitTimePadding', 'number', false], @@ -160,9 +160,9 @@ function isValidConfig({options: analyticsOptions}) { let [property, type, required] = failedValidation; if (required) { - utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to exist and of type '${type}'`); + logError(`OpenXAnalyticsAdapter: Expected '${property}' to exist and of type '${type}'`); } else { - utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to be type '${type}'`); + logError(`OpenXAnalyticsAdapter: Expected '${property}' to be type '${type}'`); } } @@ -170,8 +170,8 @@ function isValidConfig({options: analyticsOptions}) { } function buildCampaignFromUtmCodes() { - const location = utils.getWindowLocation(); - const queryParams = utils.parseQS(location && location.search); + const location = getWindowLocation(); + const queryParams = parseQS(location && location.search); let campaign = {}; UTM_TAGS.forEach(function(utmKey) { @@ -231,7 +231,7 @@ function detectBrowser() { } function prebidAnalyticsEventHandler({eventType, args}) { - utils.logMessage(eventType, Object.assign({}, args)); + logMessage(eventType, Object.assign({}, args)); switch (eventType) { case AUCTION_INIT: onAuctionInit(args); @@ -271,7 +271,7 @@ function prebidAnalyticsEventHandler({eventType, args}) { * @property {Array} bidsReceived //: [] * @property {Array} winningBids //: [] * @property {number} timeout //: 3000 - * @property {Object} config //: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1, enableV2: true}/* + * @property {Object} config //: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1}/* */ function onAuctionInit({auctionId, timestamp: startTime, timeout, adUnitCodes}) { @@ -401,8 +401,8 @@ function onBidResponse(bidResponse) { } function onBidTimeout(args) { - utils._each(args, ({auctionId, adUnitCode, bidId: requestId}) => { - let timedOutRequest = utils.deepAccess(auctionMap, + _each(args, ({auctionId, adUnitCode, bidId: requestId}) => { + let timedOutRequest = deepAccess(auctionMap, `${auctionId}.adUnitCodeToAdUnitMap.${adUnitCode}.bidRequestsMap.${requestId}`); if (timedOutRequest) { @@ -433,11 +433,22 @@ function onAuctionEnd(endedAuction) { */ function onBidWon(bidResponse) { const { auctionId, adUnitCode, requestId, adId } = bidResponse; - let winningBid = utils.deepAccess(auctionMap, + let winningBid = deepAccess(auctionMap, `${auctionId}.adUnitCodeToAdUnitMap.${adUnitCode}.bidRequestsMap.${requestId}.bids.${adId}`); if (winningBid) { - winningBid.winner = true + winningBid.winner = true; + const auction = auctionMap[auctionId]; + if (auction.sent) { + const endpoint = (analyticsConfig.endpoint || ENDPOINT) + 'event'; + const bidder = auction.adUnitCodeToAdUnitMap[adUnitCode].bidRequestsMap[requestId].bidder; + ajax(`${endpoint}?t=win&b=${adId}&a=${analyticsConfig.orgId}&bidder=${bidder}&ts=${auction.startTime}`, + () => { + logInfo(`Openx Analytics - Sending complete impression event for ${adId} at ${Date.now()}`) + }); + } else { + logInfo(`Openx Analytics - impression event for ${adId} will be sent with auction data`) + } } } @@ -511,7 +522,7 @@ function isAtf(elementId, scrollLeft = 0, scrollTop = 0) { } } } else { - utils.logWarn('OX: DOM element not for id ' + elementId); + logWarn('OX: DOM element not for id ' + elementId); } return isAtf; } @@ -529,17 +540,20 @@ function getPageOffset() { } function delayedSend(auction) { + if (auction.sent) { + return; + } const delayTime = auction.adunitCodesRenderedCount === auction.adUnitCodesCount ? analyticsConfig.payloadWaitTime : analyticsConfig.payloadWaitTime + analyticsConfig.payloadWaitTimePadding; auction.auctionSendDelayTimer = setTimeout(() => { + auction.sent = true; // any BidWon emitted after this will be recorded separately let payload = JSON.stringify([buildAuctionPayload(auction)]); - ajax(ENDPOINT, deleteAuctionMap, payload, { contentType: 'application/json' }); - function deleteAuctionMap() { - delete auctionMap[auction.id]; - } + ajax(analyticsConfig.endpoint || ENDPOINT, () => { + logInfo(`OpenX Analytics - Sending complete auction at ${Date.now()}`); + }, payload, { contentType: 'application/json' }); }, delayTime); } @@ -565,15 +579,15 @@ function getPathToBidResponseByBidId(bidId) { return []; } - utils._each(auctionMap, currentAuction => { + _each(auctionMap, currentAuction => { // skip completed auctions if (currentAuction.state === AUCTION_STATES.COMPLETED) { return; } - utils._each(currentAuction.adUnitCodeToAdUnitMap, (currentAdunit) => { - utils._each(currentAdunit.bidRequestsMap, currentBiddRequest => { - utils._each(currentBiddRequest.bids, (currentBidResponse, bidResponseId) => { + _each(currentAuction.adUnitCodeToAdUnitMap, (currentAdunit) => { + _each(currentAdunit.bidRequestsMap, currentBiddRequest => { + _each(currentBiddRequest.bids, (currentBidResponse, bidResponseId) => { if (bidId === bidResponseId) { auction = currentAuction; adUnit = currentAdunit; @@ -590,12 +604,12 @@ function getAuctionByGoogleTagSLot(slot) { let slotAdunitCodes = [slot.getSlotElementId(), slot.getAdUnitPath()]; let slotAuction; - utils._each(auctionMap, auction => { + _each(auctionMap, auction => { if (auction.state === AUCTION_STATES.COMPLETED) { return; } - utils._each(auction.adUnitCodeToAdUnitMap, (bidderRequestIdMap, adUnitCode) => { + _each(auction.adUnitCodeToAdUnitMap, (bidderRequestIdMap, adUnitCode) => { if (includes(slotAdunitCodes, adUnitCode)) { slotAuction = auction; } @@ -606,15 +620,19 @@ function getAuctionByGoogleTagSLot(slot) { } function buildAuctionPayload(auction) { - let {startTime, endTime, state, timeout, auctionOrder, userIds, adUnitCodeToAdUnitMap} = auction; - let {orgId, publisherPlatformId, publisherAccountId, campaign} = analyticsConfig; + let {startTime, endTime, state, timeout, auctionOrder, userIds, adUnitCodeToAdUnitMap, id} = auction; + const auctionId = id; + let {orgId, publisherPlatformId, publisherAccountId, campaign, testCode, configId, optimizerConfig} = analyticsConfig; return { + auctionId, adapterVersion: ADAPTER_VERSION, schemaVersion: SCHEMA_VERSION, orgId, publisherPlatformId, publisherAccountId, + configId, + optimizerConfig, campaign, state, startTime, @@ -624,14 +642,14 @@ function buildAuctionPayload(auction) { deviceType: detectMob() ? 'Mobile' : 'Desktop', deviceOSType: detectOS(), browser: detectBrowser(), - testCode: analyticsConfig.testCode, + testCode: testCode, // return an array of module name that have user data userIdProviders: buildUserIdProviders(userIds), adUnits: buildAdUnitsPayload(adUnitCodeToAdUnitMap), }; function buildAdUnitsPayload(adUnitCodeToAdUnitMap) { - return utils._map(adUnitCodeToAdUnitMap, (adUnit) => { + return _map(adUnitCodeToAdUnitMap, (adUnit) => { let {code, adPosition} = adUnit; return { @@ -641,7 +659,7 @@ function buildAuctionPayload(auction) { }; function buildBidRequestPayload(bidRequestsMap) { - return utils._map(bidRequestsMap, (bidRequest) => { + return _map(bidRequestsMap, (bidRequest) => { let {bidder, source, bids, mediaTypes, timeLimit, timedOut} = bidRequest; return { bidder, @@ -651,8 +669,9 @@ function buildAuctionPayload(auction) { availableMediaTypes: getMediaTypes(mediaTypes), timeLimit, timedOut, - bidResponses: utils._map(bidRequest.bids, (bidderBidResponse) => { + bidResponses: _map(bidRequest.bids, (bidderBidResponse) => { let { + adId, cpm, creativeId, ts, @@ -671,6 +690,7 @@ function buildAuctionPayload(auction) { } = bidderBidResponse; return { + bidId: adId, microCpm: cpm * 1000000, netRevenue, currency, @@ -696,11 +716,11 @@ function buildAuctionPayload(auction) { } function buildUserIdProviders(userIds) { - return utils._map(userIds, (userId) => { - return utils._map(userId, (id, module) => { + return _map(userIds, (userId) => { + return _map(userId, (id, module) => { return hasUserData(module, id) ? module : false }).filter(module => module); - }).reduce(utils.flatten, []).filter(utils.uniques).sort(); + }).reduce(flatten, []).filter(uniques).sort(); } function hasUserData(module, idOrIdObject) { @@ -708,7 +728,7 @@ function buildAuctionPayload(auction) { switch (module) { case 'digitrustid': - normalizedId = utils.deepAccess(idOrIdObject, 'data.id'); + normalizedId = deepAccess(idOrIdObject, 'data.id'); break; case 'lipb': normalizedId = idOrIdObject.lipbid; @@ -717,17 +737,17 @@ function buildAuctionPayload(auction) { normalizedId = idOrIdObject; } - return !utils.isEmpty(normalizedId); + return !isEmpty(normalizedId); } function getMediaTypeSizes(mediaTypes) { - return utils._map(mediaTypes, (mediaTypeConfig, mediaType) => { - return utils.parseSizesInput(mediaTypeConfig.sizes) + return _map(mediaTypes, (mediaTypeConfig, mediaType) => { + return parseSizesInput(mediaTypeConfig.sizes) .map(size => `${mediaType}_${size}`); - }).reduce(utils.flatten, []); + }).reduce(flatten, []); } function getMediaTypes(mediaTypes) { - return utils._map(mediaTypes, (mediaTypeConfig, mediaType) => mediaType); + return _map(mediaTypes, (mediaTypeConfig, mediaType) => mediaType); } } diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 8a455f6fa25..60b441e2c10 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -1,9 +1,13 @@ +import { deepAccess, convertTypes, isArray, inIframe, _map, deepSetValue, _each, parseSizesInput, parseUrl } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import includes from 'core-js-pure/features/array/includes.js' const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', + 'startdelay', 'skippable', 'playbackmethod', 'api', 'protocols', 'boxingallowed', + 'linearity', 'delivery', 'protocol', 'placement', 'minbitrate', 'maxbitrate']; const BIDDER_CODE = 'openx'; const BIDDER_CONFIG = 'hb_pb'; const BIDDER_VERSION = '3.0.3'; @@ -27,10 +31,23 @@ export const USER_ID_CODE_TO_QUERY_ARG = { parrableId: 'parrableid', // Parrable ID pubcid: 'pubcid', // PubCommon ID quantcastId: 'quantcastid', // Quantcast ID - sharedId: 'sharedid', // Shared ID User ID tapadId: 'tapadid', // Tapad Id tdid: 'ttduuid', // The Trade Desk Unified ID - verizonMediaId: 'verizonmediaid', // Verizon Media ConnectID + uid2: 'uid2', // Unified ID 2.0 + flocId: 'floc', // Chrome FLoC, + admixerId: 'admixerid', // AdMixer ID + deepintentId: 'deepintentid', // DeepIntent ID + dmdId: 'dmdid', // DMD Marketing Corp ID + nextrollId: 'nextrollid', // NextRoll ID + novatiq: 'novatiqid', // Novatiq ID + mwOpenLinkId: 'mwopenlinkid', // MediaWallah OpenLink ID + dapId: 'dapid', // Akamai DAP ID + amxId: 'amxid', // AMX RTB ID + kpuid: 'kpuid', // Kinesso ID + publinkId: 'publinkid', // Publisher Link + naveggId: 'naveggid', // Navegg ID + imuid: 'imuid', // IM-UID by Intimate Merger + adtelligentId: 'adtelligentid' // Adtelligent ID }; export const spec = { @@ -39,8 +56,8 @@ export const spec = { supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { const hasDelDomainOrPlatform = bidRequest.params.delDomain || bidRequest.params.platform; - if (utils.deepAccess(bidRequest, 'mediaTypes.banner') && hasDelDomainOrPlatform) { - return !!bidRequest.params.unit || utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; + if (deepAccess(bidRequest, 'mediaTypes.banner') && hasDelDomainOrPlatform) { + return !!bidRequest.params.unit || deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; } return !!(bidRequest.params.unit && hasDelDomainOrPlatform); @@ -75,8 +92,8 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let url = utils.deepAccess(responses, '0.body.ads.pixels') || - utils.deepAccess(responses, '0.body.pixels') || + let url = deepAccess(responses, '0.body.ads.pixels') || + deepAccess(responses, '0.body.pixels') || generateDefaultSyncUrl(gdprConsent, uspConsent); return [{ @@ -86,7 +103,7 @@ export const spec = { } }, transformBidParams: function(params, isOpenRtb) { - return utils.convertTypes({ + return convertTypes({ 'unit': 'string', 'customFloor': 'number' }, params); @@ -111,7 +128,7 @@ function generateDefaultSyncUrl(gdprConsent, uspConsent) { } function isVideoRequest(bidRequest) { - return (utils.deepAccess(bidRequest, 'mediaTypes.video') && !utils.deepAccess(bidRequest, 'mediaTypes.banner')) || bidRequest.mediaType === VIDEO; + return (deepAccess(bidRequest, 'mediaTypes.video') && !deepAccess(bidRequest, 'mediaTypes.banner')) || bidRequest.mediaType === VIDEO; } function createBannerBidResponses(oxResponseObj, {bids, startTime}) { @@ -203,7 +220,7 @@ function getViewportDimensions(isIfr) { function formatCustomParms(customKey, customParams) { let value = customParams[customKey]; - if (utils.isArray(value)) { + if (isArray(value)) { // if value is an array, join them with commas first value = value.join(','); } @@ -228,7 +245,7 @@ function getMediaTypeFromRequest(serverRequest) { } function buildCommonQueryParamsFromBids(bids, bidderRequest) { - const isInIframe = utils.inIframe(); + const isInIframe = inIframe(); let defaultParams; defaultParams = { @@ -240,10 +257,20 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { tws: getViewportDimensions(isInIframe), be: 1, bc: bids[0].params.bc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, - dddid: utils._map(bids, bid => bid.transactionId).join(','), + dddid: _map(bids, bid => bid.transactionId).join(','), nocache: new Date().getTime() }; + const userDataSegments = buildFpdQueryParams('ortb2.user.data'); + if (userDataSegments.length > 0) { + defaultParams.sm = userDataSegments; + } + + const siteContentDataSegments = buildFpdQueryParams('ortb2.site.content.data'); + if (siteContentDataSegments.length > 0) { + defaultParams.scsm = siteContentDataSegments; + } + if (bids[0].params.platform) { defaultParams.ph = bids[0].params.platform; } @@ -269,8 +296,8 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { } // normalize publisher common id - if (utils.deepAccess(bids[0], 'crumbs.pubcid')) { - utils.deepSetValue(bids[0], 'userId.pubcid', utils.deepAccess(bids[0], 'crumbs.pubcid')); + if (deepAccess(bids[0], 'crumbs.pubcid')) { + deepSetValue(bids[0], 'userId.pubcid', deepAccess(bids[0], 'crumbs.pubcid')); } defaultParams = appendUserIdsToQueryParams(defaultParams, bids[0].userId); @@ -282,14 +309,49 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { return defaultParams; } +function buildFpdQueryParams(fpdPath) { + const firstPartyData = config.getConfig(fpdPath); + if (!Array.isArray(firstPartyData) || !firstPartyData.length) { + return ''; + } + const fpd = firstPartyData + .filter( + data => (Array.isArray(data.segment) && + data.segment.length > 0 && + data.name !== undefined && + data.name.length > 0) + ) + .reduce((acc, data) => { + const name = typeof data.ext === 'object' && data.ext.segtax ? `${data.name}/${data.ext.segtax}` : data.name; + acc[name] = (acc[name] || []).concat(data.segment.map(seg => seg.id)); + return acc; + }, {}) + return Object.keys(fpd) + .map((name, _) => name + ':' + fpd[name].join('|')) + .join(',') +} + function appendUserIdsToQueryParams(queryParams, userIds) { - utils._each(userIds, (userIdObjectOrValue, userIdProviderKey) => { + _each(userIds, (userIdObjectOrValue, userIdProviderKey) => { const key = USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]; if (USER_ID_CODE_TO_QUERY_ARG.hasOwnProperty(userIdProviderKey)) { switch (userIdProviderKey) { + case 'merkleId': + queryParams[key] = userIdObjectOrValue.id; + break; + case 'flocId': + queryParams[key] = userIdObjectOrValue.id; + break; + case 'uid2': + queryParams[key] = userIdObjectOrValue.id; + break; case 'lipb': queryParams[key] = userIdObjectOrValue.lipbid; + if (Array.isArray(userIdObjectOrValue.segments) && userIdObjectOrValue.segments.length > 0) { + const liveIntentSegments = 'liveintent:' + userIdObjectOrValue.segments.join('|') + queryParams.sm = `${queryParams.sm ? queryParams.sm + ',' : ''}${liveIntentSegments}`; + } break; case 'parrableId': queryParams[key] = userIdObjectOrValue.eid; @@ -297,6 +359,9 @@ function appendUserIdsToQueryParams(queryParams, userIds) { case 'id5id': queryParams[key] = userIdObjectOrValue.uid; break; + case 'novatiq': + queryParams[key] = userIdObjectOrValue.snowflake; + break; default: queryParams[key] = userIdObjectOrValue; } @@ -323,10 +388,15 @@ function buildOXBannerRequest(bids, bidderRequest) { let customParamsForAllBids = []; let hasCustomParam = false; let queryParams = buildCommonQueryParamsFromBids(bids, bidderRequest); - let auids = utils._map(bids, bid => bid.params.unit); + let auids = _map(bids, bid => bid.params.unit); - queryParams.aus = utils._map(bids, bid => utils.parseSizesInput(bid.mediaTypes.banner.sizes).join(',')).join('|'); - queryParams.divIds = utils._map(bids, bid => encodeURIComponent(bid.adUnitCode)).join(','); + queryParams.aus = _map(bids, bid => parseSizesInput(bid.mediaTypes.banner.sizes).join(',')).join('|'); + queryParams.divids = _map(bids, bid => encodeURIComponent(bid.adUnitCode)).join(','); + // gpid + queryParams.aucs = _map(bids, function (bid) { + let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + return encodeURIComponent(gpid || '') + }).join(','); if (auids.some(auid => auid)) { queryParams.auid = auids.join(','); @@ -342,7 +412,7 @@ function buildOXBannerRequest(bids, bidderRequest) { bids.forEach(function (bid) { if (bid.params.customParams) { - let customParamsForBid = utils._map(Object.keys(bid.params.customParams), customKey => formatCustomParms(customKey, bid.params.customParams)); + let customParamsForBid = _map(Object.keys(bid.params.customParams), customKey => formatCustomParms(customKey, bid.params.customParams)); let formattedCustomParams = window.btoa(customParamsForBid.join('&')); hasCustomParam = true; customParamsForAllBids.push(formattedCustomParams); @@ -382,35 +452,55 @@ function buildOXVideoRequest(bid, bidderRequest) { } function generateVideoParameters(bid, bidderRequest) { + const videoMediaType = deepAccess(bid, `mediaTypes.video`); let queryParams = buildCommonQueryParamsFromBids([bid], bidderRequest); - let oxVideoConfig = utils.deepAccess(bid, 'params.video') || {}; - let context = utils.deepAccess(bid, 'mediaTypes.video.context'); - let playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + let oxVideoConfig = deepAccess(bid, 'params.video') || {}; + let context = deepAccess(bid, 'mediaTypes.video.context'); + let playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); let width; let height; // normalize config for video size - if (utils.isArray(bid.sizes) && bid.sizes.length === 2 && !utils.isArray(bid.sizes[0])) { + if (isArray(bid.sizes) && bid.sizes.length === 2 && !isArray(bid.sizes[0])) { width = parseInt(bid.sizes[0], 10); height = parseInt(bid.sizes[1], 10); - } else if (utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { + } else if (isArray(bid.sizes) && isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { width = parseInt(bid.sizes[0][0], 10); height = parseInt(bid.sizes[0][1], 10); - } else if (utils.isArray(playerSize) && playerSize.length === 2) { + } else if (isArray(playerSize) && playerSize.length === 2) { width = parseInt(playerSize[0], 10); height = parseInt(playerSize[1], 10); } - Object.keys(oxVideoConfig).forEach(function (key) { - if (key === 'openrtb') { - oxVideoConfig[key].w = width || oxVideoConfig[key].w; - oxVideoConfig[key].v = height || oxVideoConfig[key].v; - queryParams[key] = JSON.stringify(oxVideoConfig[key]); - } else if (!(key in queryParams) && key !== 'url') { - // only allow video-related attributes - queryParams[key] = oxVideoConfig[key]; - } - }); + let openRtbParams = {w: width, h: height}; + + // legacy openrtb params could be in video, openrtb, or video.openrtb + let legacyParams = bid.params.video || bid.params.openrtb || {}; + if (legacyParams.openrtb) { + legacyParams = legacyParams.openrtb; + } + // support for video object or full openrtb object + if (isArray(legacyParams.imp)) { + legacyParams = legacyParams.imp[0].video; + } + Object.keys(legacyParams) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => openRtbParams[param] = legacyParams[param]); + + // 5.0 openrtb video params + Object.keys(videoMediaType) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => openRtbParams[param] = videoMediaType[param]); + + let openRtbReq = { + imp: [ + { + video: openRtbParams + } + ] + } + + queryParams['openrtb'] = JSON.stringify(openRtbReq); queryParams.auid = bid.params.unit; // override prebid config with openx config if available @@ -429,6 +519,11 @@ function generateVideoParameters(bid, bidderRequest) { queryParams.vtest = 1; } + let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + queryParams.aucs = encodeURIComponent(gpid) + } + // each video bid makes a separate request enrichQueryWithFloors(queryParams, VIDEO, [bid]); @@ -439,7 +534,7 @@ function createVideoBidResponses(response, {bid, startTime}) { let bidResponses = []; if (response !== undefined && response.vastUrl !== '' && response.pub_rev > 0) { - let vastQueryParams = utils.parseUrl(response.vastUrl).search || {}; + let vastQueryParams = parseUrl(response.vastUrl).search || {}; let bidResponse = {}; bidResponse.requestId = bid.bidId; if (response.deal_id) { diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index 965b8ee1948..68e41a93b18 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -98,7 +98,7 @@ pbjs.setConfig({ ``` # Additional Details -[Banner Ads](https://docs.openx.com/Content/developers/containers/prebid-adapter.html) +[Banner Ads](https://docs.openx.com/publishers/prebid-adapter-web/) -[Video Ads](https://docs.openx.com/Content/developers/containers/prebid-video-adapter.html) +[Video Ads](https://docs.openx.com/publishers/prebid-adapter-video/) diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js new file mode 100644 index 00000000000..61ea8cdcb76 --- /dev/null +++ b/modules/operaadsBidAdapter.js @@ -0,0 +1,823 @@ +import { logWarn, isArray, isStr, triggerPixel, deepAccess, deepSetValue, isPlainObject, generateUUID, parseUrl, isFn, getDNT, logError } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; +import { OUTSTREAM } from '../src/video.js'; + +const BIDDER_CODE = 'operaads'; + +const ENDPOINT = 'https://s.adx.opera.com/ortb/v2/'; +const USER_SYNC_ENDPOINT = 'https://s.adx.opera.com/usersync/page'; + +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_LANGUAGE = 'en'; +const NET_REVENUE = true; + +const BANNER_DEFAULTS = { + SIZE: [300, 250] +} + +const VIDEO_DEFAULTS = { + PROTOCOLS: [2, 3, 5, 6], + MIMES: ['video/mp4'], + PLAYBACK_METHODS: [1, 2, 3, 4], + DELIVERY: [1], + API: [1, 2, 5], + SIZE: [640, 480] +} + +const NATIVE_DEFAULTS = { + IMAGE_TYPE: { + ICON: 1, + MAIN: 3, + }, + ASSET_ID: { + TITLE: 1, + IMAGE: 2, + ICON: 3, + BODY: 4, + SPONSORED: 5, + CTA: 6 + }, + DATA_ASSET_TYPE: { + SPONSORED: 1, + DESC: 2, + CTA_TEXT: 12, + }, + LENGTH: { + TITLE: 90, + BODY: 140, + SPONSORED: 25, + CTA: 20 + } +} + +export const spec = { + code: BIDDER_CODE, + + // short code + aliases: ['opera'], + + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid + * @returns boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!bid) { + logWarn(BIDDER_CODE, 'Invalid bid,', bid); + return false; + } + + if (!bid.params) { + logWarn(BIDDER_CODE, 'bid.params is required.') + return false; + } + + if (!bid.params.placementId) { + logWarn(BIDDER_CODE, 'bid.params.placementId is required.') + return false; + } + + if (!bid.params.endpointId) { + logWarn(BIDDER_CODE, 'bid.params.endpointId is required.') + return false; + } + + if (!bid.params.publisherId) { + logWarn(BIDDER_CODE, 'bid.params.publisherId is required.') + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} validBidRequests An array of bidRequest objects + * @param {bidderRequest} bidderRequest The master bidRequest object. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(validBidRequest => (buildOpenRtbBidRequest(validBidRequest, bidderRequest))) + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + let bidResponses = []; + + let serverBody; + if ((serverBody = serverResponse.body) && serverBody.seatbid && isArray(serverBody.seatbid)) { + serverBody.seatbid.forEach((seatbidder) => { + if (seatbidder.bid && isArray(seatbidder.bid)) { + bidResponses = seatbidder.bid.map((bid) => buildBidResponse(bid, bidRequest.originalBidRequest, serverBody)); + } + }); + } + + 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. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + if ('iframeEnabled' in syncOptions && syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USER_SYNC_ENDPOINT + }]; + } + if ('pixelEnabled' in syncOptions && syncOptions.pixelEnabled) { + const pixels = deepAccess(serverResponses, '0.body.pixels') + if (Array.isArray(pixels)) { + const userSyncPixels = [] + for (const pixel of pixels) { + userSyncPixels.push({ + type: 'image', + url: pixel + }) + } + return userSyncPixels; + } + } + return []; + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * + * @param {data} timeoutData Containing timeout specific data + */ + onTimeout: function (timeoutData) { }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * + * @param {Bid} bid The bid that won the auction + */ + onBidWon: function (bid) { + if (!bid || !isStr(bid.nurl)) { + return; + } + + let winCpm, winCurr; + if (Object.prototype.hasOwnProperty.call(bid, 'originalCpm')) { + winCpm = bid.originalCpm; + winCurr = bid.originalCurrency; + } else { + winCpm = bid.cpm; + winCurr = bid.currency; + } + + triggerPixel( + bid.nurl + .replace(/\$\{AUCTION_PRICE\}/g, winCpm) + .replace(/\$\{AUCTION_CURRENCY\}/g, winCurr) + ); + }, + + /** + * Register bidder specific code, which will execute when the adserver targeting has been set for a bid from this bidder + * + * @param {Bid} bid The bid of which the targeting has been set + */ + onSetTargeting: function (bid) { } +} + +/** + * Buid openRtb request from bidRequest and bidderRequest + * + * @param {BidRequest} bidRequest + * @param {BidderRequest} bidderRequest + * @returns {Request} + */ +function buildOpenRtbBidRequest(bidRequest, bidderRequest) { + const pageReferrer = deepAccess(bidderRequest, 'refererInfo.referer'); + + // build OpenRTB request body + const payload = { + id: bidderRequest.auctionId, + tmax: bidderRequest.timeout || config.getConfig('bidderTimeout'), + test: config.getConfig('debug') ? 1 : 0, + imp: createImp(bidRequest), + device: getDevice(), + site: { + id: String(deepAccess(bidRequest, 'params.publisherId')), + domain: getDomain(pageReferrer), + page: pageReferrer, + ref: window.self === window.top ? document.referrer : '', + }, + at: 1, + bcat: getBcat(bidRequest), + cur: [DEFAULT_CURRENCY], + regs: { + coppa: config.getConfig('coppa') ? 1 : 0, + ext: {} + }, + user: { + buyeruid: getUserId(bidRequest) + } + } + + const gdprConsent = deepAccess(bidderRequest, 'gdprConsent'); + if (!!gdprConsent && gdprConsent.gdprApplies) { + deepSetValue(payload, 'regs.ext.gdpr', 1); + deepSetValue(payload, 'user.ext.consent', gdprConsent.consentString); + } + + const uspConsent = deepAccess(bidderRequest, 'uspConsent'); + if (uspConsent) { + deepSetValue(payload, 'regs.ext.us_privacy', uspConsent); + } + + const eids = deepAccess(bidRequest, 'userIdAsEids', []); + if (eids.length > 0) { + deepSetValue(payload, 'user.eids', eids); + } + + return { + method: 'POST', + url: ENDPOINT + String(deepAccess(bidRequest, 'params.publisherId')) + + '?ep=' + String(deepAccess(bidRequest, 'params.endpointId')), + data: JSON.stringify(payload), + options: { + contentType: 'application/json', + customHeaders: { + 'x-openrtb-version': 2.5 + } + }, + // set original bid request, so we can get it from interpretResponse + originalBidRequest: bidRequest + } +} + +/** + * Build bid response from openrtb bid response. + * + * @param {OpenRtbBid} bid + * @param {BidRequest} bidRequest + * @param {OpenRtbResponseBody} responseBody + * @returns {BidResponse} + */ +function buildBidResponse(bid, bidRequest, responseBody) { + let mediaType = BANNER; + let nativeResponse; + + if (/VAST\s+version/.test(bid.adm)) { + mediaType = VIDEO; + } else { + let markup; + try { + markup = JSON.parse(bid.adm); + } catch (e) { + markup = null; + } + + // OpenRtb Markup Response Object + // https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-Native-Ads-Specification-1-1_2016.pdf#5.1 + if (markup && isPlainObject(markup.native)) { + mediaType = NATIVE; + nativeResponse = markup.native; + } + } + + const currency = responseBody.cur || DEFAULT_CURRENCY; + const cpm = (parseFloat(bid.price) || 0).toFixed(2); + + const categories = deepAccess(bid, 'cat', []); + + const bidResponse = { + requestId: bid.impid, + cpm: cpm, + currency: currency, + mediaType: mediaType, + ttl: 300, + creativeId: bid.crid || bid.id, + netRevenue: NET_REVENUE, + nurl: bid.nurl, + lurl: bid.lurl, + meta: { + mediaType: mediaType, + primaryCatId: categories[0], + secondaryCatIds: categories.slice(1), + } + }; + + if (bid.adomain && isArray(bid.adomain) && bid.adomain.length > 0) { + bidResponse.meta.advertiserDomains = bid.adomain; + bidResponse.meta.clickUrl = bid.adomain[0]; + } + + switch (mediaType) { + case VIDEO: { + const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize', VIDEO_DEFAULTS.SIZE); + const size = canonicalizeSizesArray(playerSize)[0]; + + bidResponse.vastXml = bid.adm; + + bidResponse.width = bid.w || size[0]; + bidResponse.height = bid.h || size[1]; + + const context = deepAccess(bidRequest, 'mediaTypes.video.context'); + + // if outstream video, add a default render for it. + if (context === OUTSTREAM) { + // fill adResponse, will be used in ANOutstreamVideo.renderAd + bidResponse.adResponse = { + content: bidResponse.vastXml, + width: bidResponse.width, + height: bidResponse.height, + player_width: size[0], + player_height: size[1], + }; + bidResponse.renderer = createRenderer(bidRequest); + } + break; + } + case NATIVE: { + bidResponse.native = interpretNativeAd(nativeResponse, currency, cpm); + break; + } + default: { + bidResponse.ad = bid.adm; + + bidResponse.width = bid.w; + bidResponse.height = bid.h; + } + } + return bidResponse; +} + +/** + * Convert OpenRtb native response to bid native object. + * + * @param {OpenRtbNativeResponse} nativeResponse + * @param {String} currency + * @param {String} cpm + * @returns {BidNative} native + */ +function interpretNativeAd(nativeResponse, currency, cpm) { + const native = {}; + + // OpenRtb Link Object + // https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-Native-Ads-Specification-1-1_2016.pdf#5.7 + const clickUrl = deepAccess(nativeResponse, 'link.url'); + if (clickUrl && isStr(clickUrl)) { + native.clickUrl = decodeURIComponent(clickUrl); + } + + const clickTrackers = deepAccess(nativeResponse, 'link.clicktrackers'); + if (clickTrackers && isArray(clickTrackers)) { + native.clickTrackers = clickTrackers + .filter(Boolean) + .map( + url => decodeURIComponent(url) + .replace(/\$\{AUCTION_PRICE\}/g, cpm) + .replace(/\$\{AUCTION_CURRENCY\}/g, currency) + ); + } + + if (nativeResponse.imptrackers && isArray(nativeResponse.imptrackers)) { + native.impressionTrackers = nativeResponse.imptrackers + .filter(Boolean) + .map( + url => decodeURIComponent(url) + .replace(/\$\{AUCTION_PRICE\}/g, cpm) + .replace(/\$\{AUCTION_CURRENCY\}/g, currency) + ); + } + + if (nativeResponse.jstracker && isStr(nativeResponse.jstracker)) { + native.javascriptTrackers = [nativeResponse.jstracker]; + } + + let assets; + if ((assets = nativeResponse.assets) && isArray(assets)) { + assets.forEach((asset) => { + switch (asset.id) { + case NATIVE_DEFAULTS.ASSET_ID.TITLE: { + const title = deepAccess(asset, 'title.text'); + if (title) { + native.title = title; + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.IMAGE: { + if (asset.img) { + native.image = { + url: decodeURIComponent(asset.img.url), + width: asset.img.w, + height: asset.img.h + } + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.ICON: { + if (asset.img) { + native.icon = { + url: decodeURIComponent(asset.img.url), + width: asset.img.w, + height: asset.img.h + } + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.BODY: { + const body = deepAccess(asset, 'data.value'); + if (body) { + native.body = body; + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.SPONSORED: { + const sponsoredBy = deepAccess(asset, 'data.value'); + if (sponsoredBy) { + native.sponsoredBy = sponsoredBy; + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.CTA: { + const cta = deepAccess(asset, 'data.value'); + if (cta) { + native.cta = cta; + } + break; + } + } + }); + } + + return native; +} + +/** + * Create an imp array + * + * @param {BidRequest} bidRequest + * @param {Currency} cur + * @returns {Imp[]} + */ +function createImp(bidRequest) { + const imp = []; + + const impItem = { + id: bidRequest.bidId, + tagid: String(deepAccess(bidRequest, 'params.placementId')), + }; + + let mediaType, size; + let bannerReq, videoReq, nativeReq; + + if ((bannerReq = deepAccess(bidRequest, 'mediaTypes.banner'))) { + size = canonicalizeSizesArray(bannerReq.sizes || BANNER_DEFAULTS.SIZE)[0]; + + impItem.banner = { + w: size[0], + h: size[1], + pos: 0, + }; + + mediaType = BANNER; + } else if ((videoReq = deepAccess(bidRequest, 'mediaTypes.video'))) { + size = canonicalizeSizesArray(videoReq.playerSize || VIDEO_DEFAULTS.SIZE)[0]; + + impItem.video = { + w: size[0], + h: size[1], + pos: 0, + mimes: videoReq.mimes || VIDEO_DEFAULTS.MIMES, + protocols: videoReq.protocols || VIDEO_DEFAULTS.PROTOCOLS, + startdelay: typeof videoReq.startdelay === 'number' ? videoReq.startdelay : 0, + skip: typeof videoReq.skip === 'number' ? videoReq.skip : 0, + playbackmethod: videoReq.playbackmethod || VIDEO_DEFAULTS.PLAYBACK_METHODS, + delivery: videoReq.delivery || VIDEO_DEFAULTS.DELIVERY, + api: videoReq.api || VIDEO_DEFAULTS.API, + placement: videoReq.context === OUTSTREAM ? 3 : 1, + }; + + mediaType = VIDEO; + } else if ((nativeReq = deepAccess(bidRequest, 'mediaTypes.native'))) { + const params = bidRequest.nativeParams || nativeReq; + + const request = { + native: { + ver: '1.1', + assets: createNativeAssets(params), + } + }; + + impItem.native = { + ver: '1.1', + request: JSON.stringify(request), + }; + + mediaType = NATIVE; + } + + const floorDetail = getBidFloor(bidRequest, { + mediaType: mediaType || '*', + size: size || '*' + }) + + impItem.bidfloor = floorDetail.floor; + impItem.bidfloorcur = floorDetail.currency; + + if (mediaType) { + imp.push(impItem); + } + + return imp; +} + +/** + * Convert bid sizes to size array + * + * @param {Size[]|Size[][]} sizes + * @returns {Size[][]} + */ +function canonicalizeSizesArray(sizes) { + if (sizes.length === 2 && !isArray(sizes[0])) { + return [sizes]; + } + return sizes; +} + +/** + * Create Assets Object for Native request + * + * @param {Object} params + * @returns {Asset[]} + */ +function createNativeAssets(params) { + const assets = []; + + if (params.title) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.TITLE, + required: params.title.required ? 1 : 0, + title: { + len: params.title.len || NATIVE_DEFAULTS.LENGTH.TITLE + } + }) + } + + if (params.image) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.IMAGE, + required: params.image.required ? 1 : 0, + img: mapNativeImage(params.image, NATIVE_DEFAULTS.IMAGE_TYPE.MAIN) + }) + } + + if (params.icon) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.ICON, + required: params.icon.required ? 1 : 0, + img: mapNativeImage(params.icon, NATIVE_DEFAULTS.IMAGE_TYPE.ICON) + }) + } + + if (params.sponsoredBy) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.SPONSORED, + required: params.sponsoredBy.required ? 1 : 0, + data: { + type: NATIVE_DEFAULTS.DATA_ASSET_TYPE.SPONSORED, + len: params.sponsoredBy.len | NATIVE_DEFAULTS.LENGTH.SPONSORED + } + }) + } + + if (params.body) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.BODY, + required: params.body.required ? 1 : 0, + data: { + type: NATIVE_DEFAULTS.DATA_ASSET_TYPE.DESC, + len: params.body.len || NATIVE_DEFAULTS.LENGTH.BODY + } + }) + } + + if (params.cta) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.CTA, + required: params.cta.required ? 1 : 0, + data: { + type: NATIVE_DEFAULTS.DATA_ASSET_TYPE.CTA_TEXT, + len: params.cta.len || NATIVE_DEFAULTS.LENGTH.CTA + } + }) + } + + return assets; +} + +/** + * Create native image object + * + * @param {Object} image + * @param {Number} type + * @returns {NativeImage} + */ +function mapNativeImage(image, type) { + const img = { type: type }; + + if (image.aspect_ratios) { + const ratio = image.aspect_ratios[0]; + const minWidth = ratio.min_width || 100; + + img.wmin = minWidth; + img.hmin = (minWidth / ratio.ratio_width * ratio.ratio_height); + } + + if (image.sizes) { + const size = canonicalizeSizesArray(image.sizes)[0]; + + img.w = size[0]; + img.h = size[1]; + } + + return img; +} + +/** + * Get user id from bid request. if no user id module used, return a new uuid. + * + * @param {BidRequest} bidRequest + * @returns {String} userId + */ +function getUserId(bidRequest) { + let sharedId = deepAccess(bidRequest, 'userId.sharedid.id'); + if (sharedId) { + return sharedId; + } + + for (const idModule of ['pubcid', 'tdid']) { + let userId = deepAccess(bidRequest, `userId.${idModule}`); + if (userId) { + return userId; + } + } + + return generateUUID(); +} + +/** + * Get publisher domain + * + * @param {String} referer + * @returns {String} domain + */ +function getDomain(referer) { + let domain; + + if (!(domain = config.getConfig('publisherDomain'))) { + const u = parseUrl(referer); + domain = u.hostname; + } + + return domain.replace(/^https?:\/\/([\w\-\.]+)(?::\d+)?/, '$1'); +} + +/** + * Get bid floor price + * + * @param {BidRequest} bid + * @param {Params} params + * @returns {Floor} floor price + */ +function getBidFloor(bid, {mediaType = '*', size = '*'}) { + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType, + size + }); + + if (isPlainObject(floorInfo) && !isNaN(floorInfo.floor)) { + return { + currency: floorInfo.currency || DEFAULT_CURRENCY, + floor: floorInfo.floor + }; + } + } + + return { + currency: DEFAULT_CURRENCY, + floor: 0.0 + } +} + +/** + * Get bcat + * + * @param {BidRequest} bidRequest + * @returns {String[]} + */ +function getBcat(bidRequest) { + let bcat = []; + + const pBcat = deepAccess(bidRequest, 'params.bcat'); + if (pBcat) { + bcat = bcat.concat(pBcat); + } + + return bcat; +} + +/** + * Get device info + * + * @returns {Object} + */ +function getDevice() { + const device = config.getConfig('device') || {}; + + device.w = device.w || window.screen.width; + device.h = device.h || window.screen.height; + device.ua = device.ua || navigator.userAgent; + device.language = device.language || getLanguage(); + device.dnt = typeof device.dnt === 'number' + ? device.dnt : (getDNT() ? 1 : 0); + + return device; +} + +/** + * Get browser language + * + * @returns {String} language + */ +function getLanguage() { + const lang = (navigator.languages && navigator.languages[0]) || + navigator.language || navigator.userLanguage; + return lang ? lang.split('-')[0] : DEFAULT_LANGUAGE; +} + +/** + * Create render for outstream video. + * + * @param {BidRequest} bidRequest + * @returns + */ +function createRenderer(bidRequest) { + const globalRenderer = deepAccess(bidRequest, 'renderer'); + const currentRenderer = deepAccess(bidRequest, 'mediaTypes.video.renderer'); + + let url = OUTSTREAM_RENDERER_URL; + let config = {}; + let render = function (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + adResponse: bid.adResponse, + }); + }); + }; + + if (currentRenderer) { + url = currentRenderer.url; + config = currentRenderer.options; + render = currentRenderer.render; + } else if (globalRenderer) { + url = globalRenderer.url; + config = globalRenderer.options; + render = globalRenderer.render; + } + + const renderer = Renderer.install({ + id: bidRequest.bidId, + url: url, + loaded: false, + config: config, + adUnitCode: bidRequest.adUnitCode + }); + + try { + renderer.setRender(render); + } catch (e) { + logError(BIDDER_CODE, 'Error calling setRender on renderer', e); + } + return renderer; +} + +registerBidder(spec); diff --git a/modules/operaadsBidAdapter.md b/modules/operaadsBidAdapter.md new file mode 100644 index 00000000000..709c67a04a7 --- /dev/null +++ b/modules/operaadsBidAdapter.md @@ -0,0 +1,154 @@ +# OperaAds Bidder Adapter + +## Overview + +``` +Module Name: OperaAds Bidder Adapter +Module Type: Bidder Adapter +Maintainer: adtech-prebid-group@opera.com +``` + +## Description + +Module that connects to OperaAds's demand sources + +## Bid Parameters + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `placementId` | required | String | The Placement Id provided by Opera Ads. | `s5340077725248` +| `endpointId` | required | String | The Endpoint Id provided by Opera Ads. | `ep3425464070464` +| `publisherId` | required | String | The Publisher Id provided by Opera Ads. | `pub3054952966336` +| `bcat` | optional | String or String[] | The bcat value. | `IAB9-31` + +### Bid Video Parameters + +Set these parameters to `bid.mediaTypes.video`. + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `context` | optional | String | `instream` or `outstream`. | `instream` +| `mimes` | optional | String[] | Content MIME types supported. | `['video/mp4']` +| `playerSize` | optional | Number[] or Number[][] | Video player size in device independent pixels | `[[640, 480]]` +| `protocols` | optional | Number[] | Array of supported video protocls. | `[1, 2, 3, 4, 5, 6, 7, 8]` +| `startdelay` | optional | Number | Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. | `0` +| `skip` | optional | Number | Indicates if the player will allow the video to be skipped, where 0 = no, 1 = yes. | `1` +| `playbackmethod` | optional | Number[] | Playback methods that may be in use. | `[2]` +| `delivery` | optional | Number[] | Supported delivery methods. | `[1]` +| `api` | optional | Number[] | List of supported API frameworks for this impression. | `[1, 2, 5]` + +### Bid Native Parameters + +Set these parameters to `bid.nativeParams` or `bid.mediaTypes.native`. + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `title` | optional | Object | Config for native asset title. | `{required: true, len: 25}` +| `image` | optional | Object | Config for native asset image. | `{required: true, sizes: [[300, 250]], aspect_ratios: [{min_width: 300, min_height: 250, ratio_width: 1, ratio_height: 1}]}` +| `icon` | optional | Object | Config for native asset icon. | `{required: true, sizes: [[60, 60]], aspect_ratios: [{min_width: 60, min_height: 60, ratio_width: 1, ratio_height: 1}]}}` +| `sponsoredBy` | optional | Object | Config for native asset sponsoredBy. | `{required: true, len: 20}` +| `body` | optional | Object | Config for native asset body. | `{required: true, len: 200}` +| `cta` | optional | Object | Config for native asset cta. | `{required: true, len: 20}` + +## Example + +### Banner Ads + +```javascript +var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'operaads', + params: { + placementId: 's5340077725248', + endpointId: 'ep3425464070464', + publisherId: 'pub3054952966336' + } + }] +}]; +``` + +### Video Ads + +```javascript +var adUnits = [{ + code: 'video-ad-div', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1 + } + }, + bids: [{ + bidder: 'operaads', + params: { + placementId: 's5340077725248', + endpointId: 'ep3425464070464', + publisherId: 'pub3054952966336' + } + }] +}]; +``` + +* For video ads, enable prebid cache. + +```javascript +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } +}); +``` + +### Native Ads + +```javascript +var adUnits = [{ + code: 'native-ad-div', + mediaTypes: { + native: { + title: { required: true, len: 75 }, + image: { required: true, sizes: [[300, 250]] }, + body: { len: 200 }, + sponsoredBy: { len: 20 } + } + }, + bids: [{ + bidder: 'operaads', + params: { + placementId: 's5340077725248', + endpointId: 'ep3425464070464', + publisherId: 'pub3054952966336' + } + }] +}]; +``` + +### User Ids + +Opera Ads Bid Adapter uses `sharedId`, `pubcid` or `tdid`, please config at least one. + +```javascript +pbjs.setConfig({ + ..., + userSync: { + userIds: [{ + name: 'sharedId', + storage: { + name: '_sharedID', // name of the 1st party cookie + type: 'cookie', + expires: 30 + } + }] + } +}); +``` diff --git a/modules/optimeraBidAdapter.js b/modules/optimeraBidAdapter.js deleted file mode 100644 index b470e901ec6..00000000000 --- a/modules/optimeraBidAdapter.js +++ /dev/null @@ -1,85 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { deepAccess } from '../src/utils.js'; - -const BIDDER_CODE = 'optimera'; -const SCORES_BASE_URL = 'https://dyv1bugovvq1g.cloudfront.net/'; - -export const spec = { - code: BIDDER_CODE, - /** - * Determines whether or not the given bid request is valid. - * - * @param {bidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid (bidRequest) { - if (typeof bidRequest.params !== 'undefined' && typeof bidRequest.params.clientID !== 'undefined') { - return true; - } - return false; - }, - /** - * Make a server request from the list of BidRequests. - * - * We call the existing scores data file for ad slot placement scores. - * These scores will be added to the dealId to be pushed to DFP. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests (validBidRequests) { - const optimeraHost = window.location.host; - const optimeraPathName = window.location.pathname; - if (typeof validBidRequests[0].params.clientID !== 'undefined') { - const { clientID } = validBidRequests[0].params; - const scoresURL = `${SCORES_BASE_URL + clientID}/${optimeraHost}${optimeraPathName}.js`; - return { - method: 'GET', - url: scoresURL, - payload: validBidRequests, - }; - } - return {}; - }, - /** - * Unpack the response from the server into a list of bids. - * - * Some required bid params are not needed for this so default - * values are used. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse (serverResponse, bidRequest) { - const validBids = bidRequest.payload; - const bidResponses = []; - let dealId = ''; - if (typeof serverResponse.body !== 'undefined') { - const scores = serverResponse.body; - for (let i = 0; i < validBids.length; i += 1) { - if (typeof validBids[i].params.clientID !== 'undefined') { - if (validBids[i].adUnitCode in scores) { - const deviceDealId = deepAccess(scores, `device.${validBids[i].params.device}.${validBids[i].adUnitCode}`); - dealId = deviceDealId || scores[validBids[i].adUnitCode]; - } - const bidResponse = { - requestId: validBids[i].bidId, - ad: '
', - cpm: 0.01, - width: 0, - height: 0, - dealId, - ttl: 300, - creativeId: '1', - netRevenue: '0', - currency: 'USD' - }; - bidResponses.push(bidResponse); - } - } - } - return bidResponses; - } -} - -registerBidder(spec); diff --git a/modules/optimeraBidAdapter.md b/modules/optimeraBidAdapter.md deleted file mode 100644 index 25da9b6236f..00000000000 --- a/modules/optimeraBidAdapter.md +++ /dev/null @@ -1,58 +0,0 @@ -# Overview - -``` -Module Name: Optimera Bidder Adapter -Module Type: Bidder Adapter -Maintainer: kcandiotti@optimera.nyc -``` - -# Description - -Module that adds ad placement visibility scores for DFP. - -# Test Parameters -``` - var adUnits = [{ - code: 'div-1', - sizes: [[300, 250], [300,600]], - bids: [ - { - bidder: 'optimera', - params: { - clientID: '9999', - device: 'mo' - } - }] - },{ - code: 'div-0', - sizes: [[728, 90]], - bids: [ - { - bidder: 'optimera', - params: { - clientID: '9999', - device: 'mo' - } - }] - }]; -``` - -# AppNexus Issue -There is an issue where the plugin sometimes doesn't return impressions with AppNexus. - -There is an open issue here: [#3597](https://github.com/prebid/Prebid.js/issues/3597) - -## Configuration Workaround - -Optimera's configuration requires the use of size 0,0 which, in some instances, causes the AppNexus ad server to respond to ad requests but not fill impressions. AppNexus and vendors using the AppNexus ad server should monitor 3rd party numbers to ensure there is no decline in fill rate. - -Configuration Example: - -``` -code: ‘leaderboard', -mediaTypes: { - banner: { - sizes: [[970, 250],[970, 90],[970, 66],[728, 90],[320, 50],[320, 100],[300, 250],[0, 0]], - } -} -``` diff --git a/modules/optimeraRtdProvider.js b/modules/optimeraRtdProvider.js new file mode 100644 index 00000000000..024a558baca --- /dev/null +++ b/modules/optimeraRtdProvider.js @@ -0,0 +1,227 @@ +/** + * This module adds optimera provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * + * The module will fetch targeting values from the Optimera server + * and apply the tageting to each ad request. These values are created + * from the Optimera Mesaurement script which is installed on the + * Publisher's site. + * + * @module modules/optimeraRtdProvider + * @requires module:modules/realTimeData + */ + +/** + * @typedef {Object} ModuleParams + * @property {string} clientID + * @property {string} optimeraKeyName + * @property {string} device + */ + +import { logInfo, logError } from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { ajaxBuilder } from '../src/ajax.js'; + +/** @type {ModuleParams} */ +let _moduleParams = {}; + +/** + * Default Optimera Key Name + * This can default to hb_deal_optimera for publishers + * who used the previous Optimera Bidder Adapter. + * @type {string} */ +export let optimeraKeyName = 'hb_deal_optimera'; + +/** + * Optimera Score File Base URL. + * This is the base URL for the data endpoint request to fetch + * the targeting values. + * @type {string} + */ +export const scoresBaseURL = 'https://dyv1bugovvq1g.cloudfront.net/'; + +/** + * Optimera Score File URL. + * @type {string} + */ +export let scoresURL; + +/** + * Optimera Client ID. + * @type {string} + */ +export let clientID; + +/** + * Optional device parameter. + * @type {string} + */ +export let device = 'default'; + +/** + * Targeting object for all ad positions. + * @type {string} + */ +export let optimeraTargeting = {}; + +/** + * Flag to indicateo if a new score file should be fetched. + * @type {string} + */ +export let fetchScoreFile = true; + +/** + * Make the request for the Score File. + */ +export function scoreFileRequest() { + logInfo('Fetch Optimera score file.'); + const ajax = ajaxBuilder(); + ajax(scoresURL, + { + success: (res, req) => { + if (req.status === 200) { + try { + setScores(res); + } catch (err) { + logError('Unable to parse Optimera Score File.', err); + } + } else if (req.status === 403) { + logError('Unable to fetch the Optimera Score File - 403'); + } + }, + error: () => { + logError('Unable to fetch the Optimera Score File.'); + } + }); +} + +/** + * Apply the Optimera targeting to the ad slots. + */ +export function returnTargetingData(adUnits, config) { + const targeting = {}; + try { + adUnits.forEach(function(adUnit) { + if (optimeraTargeting[adUnit]) { + targeting[adUnit] = {}; + targeting[adUnit][optimeraKeyName] = [optimeraTargeting[adUnit]]; + } + }); + } catch (err) { + logError('error', err); + } + logInfo('Apply Optimera targeting'); + return targeting; +} + +/** + * Fetch a new score file when an auction starts. + * Only fetch the new file if a new score file is needed. + */ +export function onAuctionInit(auctionDetails, config, userConsent) { + setScoresURL(); + if (fetchScoreFile) { + scoreFileRequest(); + } +} + +/** + * Initialize the Module. + */ +export function init(moduleConfig) { + _moduleParams = moduleConfig.params; + if (_moduleParams && _moduleParams.clientID) { + clientID = _moduleParams.clientID; + if (_moduleParams.optimeraKeyName) { + optimeraKeyName = (_moduleParams.optimeraKeyName); + } + if (_moduleParams.device) { + device = _moduleParams.device; + } + setScoresURL(); + scoreFileRequest(); + return true; + } else { + if (!_moduleParams.clientID) { + logError('Optimera clientID is missing in the Optimera RTD configuration.'); + } + return false; + } +} + +/** + * Set the score file url. + * + * This fully-formed URL is for the data endpoint request to fetch + * the targeting values. This is not a js library, rather JSON + * which has the targeting values for the page. + * + * The score file url is based on the web page url. If the new score file URL + * has been updated, set the fetchScoreFile flag to true to is can be fetched. + * + */ +export function setScoresURL() { + const optimeraHost = window.location.host; + const optimeraPathName = window.location.pathname; + let newScoresURL = `${scoresBaseURL}${clientID}/${optimeraHost}${optimeraPathName}.js`; + if (scoresURL !== newScoresURL) { + scoresURL = newScoresURL; + fetchScoreFile = true; + } else { + fetchScoreFile = false; + } +} + +/** + * Set the scores for the device if given. + * Add any any insights to the winddow.optimeraInsights object. + * + * @param {*} result + * @returns {string} JSON string of Optimera Scores. + */ +export function setScores(result) { + let scores = {}; + try { + scores = JSON.parse(result); + if (device !== 'default' && scores.device[device]) { + scores = scores.device[device]; + } + logInfo(scores); + if (scores.insights) { + window.optimeraInsights = window.optimeraInsights || {}; + window.optimeraInsights.data = scores.insights; + } + } catch (e) { + logError('Optimera score file could not be parsed.'); + } + optimeraTargeting = scores; +} + +/** @type {RtdSubmodule} */ +export const optimeraSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: 'optimeraRTD', + /** + * get data when an auction starts + * @function + */ + onAuctionInitEvent: onAuctionInit, + /** + * get data and send back to realTimeData module + * @function + */ + getTargetingData: returnTargetingData, + init, +}; + +/** + * Register the Sub Module. + */ +function registerSubModule() { + submodule('realTimeData', optimeraSubmodule); +} + +registerSubModule(); diff --git a/modules/optimeraRtdProvider.md b/modules/optimeraRtdProvider.md new file mode 100644 index 00000000000..610dec537e0 --- /dev/null +++ b/modules/optimeraRtdProvider.md @@ -0,0 +1,44 @@ +# Overview +``` +Module Name: Optimera Real Time Date Module +Module Type: RTD Module +Maintainer: mcallari@optimera.nyc +``` + +# Description + +Optimera Real Time Data Module. Provides targeting for ad requests from data collected by the Optimera Measurement script on your site. Please contact [Optimera](http://optimera.nyc/) for information. This is a port of the Optimera Bidder Adapter. + +# Configurations + +Compile the Optimera RTD Provider into your Prebid build: + +`gulp build --modules=optimeraRtdProvider` + +Configuration example for using RTD module with `optimera` provider +```javascript + pbjs.setConfig({ + realTimeData: { + dataProviders: [ + { + name: 'optimeraRTD', + waitForIt: true, + params: { + clientID: '9999', + optimeraKeyName: 'optimera', + device: 'de' + } + } + ] + } +``` + +#Params + +Contact Optimera to get assistance with the params. + +| param name | type |Scope | Description | +| :------------ | :------------ | :------- | :------- | +| clientID | string | required | Optimera Client ID | +| optimeraKeyName | string | optional | GAM key name for Optimera. If migrating from the Optimera bidder adapter this will default to hb_deal_optimera and can be ommitted from the configuration. | +| device | string | optional | Device type code for mobile, tablet, or desktop. Either mo, tb, de | diff --git a/modules/optoutBidAdapter.js b/modules/optoutBidAdapter.js new file mode 100644 index 00000000000..d218a65bf90 --- /dev/null +++ b/modules/optoutBidAdapter.js @@ -0,0 +1,85 @@ +import { deepAccess } from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'optout'; + +function getDomain(bidderRequest) { + return deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || deepAccess(window, 'location.href'); +} + +function getCurrency() { + let cur = config.getConfig('currency'); + if (cur === undefined) { + cur = { + adServerCurrency: 'EUR', + granularityMultiplier: 1 + }; + } + return cur; +} + +function hasPurpose1Consent(bidderRequest) { + let result = false; + if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.apiVersion === 2) { + result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); + } + } + return result; +} + +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function(bid) { + return !!bid.params.publisher && !!bid.params.adslot; + }, + + buildRequests: function(validBidRequests) { + return validBidRequests.map(bidRequest => { + let endPoint = 'https://adscience-nocookie.nl/prebid/display'; + let consentString = ''; + let gdpr = 0; + if (bidRequest.gdprConsent) { + gdpr = (typeof bidRequest.gdprConsent.gdprApplies === 'boolean') ? Number(bidRequest.gdprConsent.gdprApplies) : 0; + consentString = bidRequest.gdprConsent.consentString; + if (!gdpr || hasPurpose1Consent(bidRequest)) { + endPoint = 'https://prebid.adscience.nl/prebid/display'; + } + } + return { + method: 'POST', + url: endPoint, + data: { + requestId: bidRequest.bidId, + publisher: bidRequest.params.publisher, + adSlot: bidRequest.params.adslot, + cur: getCurrency(), + url: getDomain(bidRequest), + ortb2: config.getConfig('ortb2'), + consent: consentString, + gdpr: gdpr + + }, + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + return serverResponse.body; + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (gdprConsent) { + let gdpr = (typeof gdprConsent.gdprApplies === 'boolean') ? Number(gdprConsent.gdprApplies) : 0; + if (syncOptions.iframeEnabled && (!gdprConsent.gdprApplies || hasPurpose1Consent({gdprConsent}))) { + return [{ + type: 'iframe', + url: 'https://umframe.adscience.nl/matching/iframe?gdpr=' + gdpr + '&gdpr_consent=' + gdprConsent.consentString + }]; + } + } + }, +}; +registerBidder(spec); diff --git a/modules/optoutBidAdapter.md b/modules/optoutBidAdapter.md new file mode 100644 index 00000000000..de70f3e3569 --- /dev/null +++ b/modules/optoutBidAdapter.md @@ -0,0 +1,27 @@ +# Overview +Module Name: Opt Out Advertising Bidder Adapter Module +Type: Bidder Adapter +Maintainer: rob@optoutadvertising.com + +# Description +Opt Out Advertising Bidder Adapter for Prebid.js. + +# Test Parameters +``` +var adUnits = [ +{ + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: 'optout', + params: { + publisher: '8', + adslot: 'prebid_demo', + } + } + ] +} +]; +``` + diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index e01746af487..111c1876e14 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -1,12 +1,53 @@ +import { isFn, isPlainObject } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; const storageManager = getStorageManager(); +/** + * Determines whether or not the given bid response is valid. + * + * @param {object} bidResponse The bid response to validate. + * @return boolean True if this is a valid bid response, and false if it is not valid. + */ +function isBidResponseValid(bidResponse) { + let requiredKeys = ['requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency']; + + switch (bidResponse.mediaType) { + case BANNER: + requiredKeys = requiredKeys.concat(['width', 'height', 'ad']); + break; + case NATIVE: + if (!bidResponse.native.hasOwnProperty('impressionTrackers')) { + return false + } + break; + default: + return false + } + + for (const key of requiredKeys) { + if (!bidResponse.hasOwnProperty(key)) { + return false + } + } + + return true +} + export const spec = { code: 'orbidder', + gvlid: 559, hostname: 'https://orbidder.otto.de', + supportedMediaTypes: [BANNER, NATIVE], + /** + * Returns a customzied hostname if 'ov_orbidder_host' is set in the browser's local storage. + * This is only used for integration testing. + * + * @return The hostname bid requests should be sent to. + */ getHostname() { let ret = this.hostname; try { @@ -16,14 +57,26 @@ export const spec = { return ret; }, + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false if it is not valid. + */ isBidRequestValid(bid) { return !!(bid.sizes && bid.bidId && bid.params && (bid.params.accountId && (typeof bid.params.accountId === 'string')) && (bid.params.placementId && (typeof bid.params.placementId === 'string')) && - ((typeof bid.params.bidfloor === 'undefined') || (typeof bid.params.bidfloor === 'number')) && ((typeof bid.params.profile === 'undefined') || (typeof bid.params.profile === 'object'))); }, + /** + * Build a request from the list of valid BidRequests that will be sent by prebid to the orbidder /bid endpoint, i.e. the server. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the orbidder /bid endpoint, + * i.e. the server. + * @return The requests for the orbidder /bid endpoint, i.e. the server. + */ buildRequests(validBidRequests, bidderRequest) { const hostname = this.getHostname(); return validBidRequests.map((bidRequest) => { @@ -32,7 +85,9 @@ export const spec = { referer = bidderRequest.refererInfo.referer || ''; } - const ret = { + bidRequest.params.bidfloor = getBidFloor(bidRequest); + + let httpReq = { url: `${hostname}/bid`, method: 'POST', options: { withCredentials: true }, @@ -44,37 +99,66 @@ export const spec = { transactionId: bidRequest.transactionId, adUnitCode: bidRequest.adUnitCode, bidRequestCount: bidRequest.bidRequestCount, + params: bidRequest.params, sizes: bidRequest.sizes, - params: bidRequest.params + mediaTypes: bidRequest.mediaTypes } }; + if (bidderRequest && bidderRequest.gdprConsent) { - ret.data.gdprConsent = { + httpReq.data.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') && bidderRequest.gdprConsent.gdprApplies }; } - return ret; + return httpReq; }); }, + /** + * Unpack the response from the orbidder /bid endpoint into a list of bids. + * + * @param {*} serverResponse A successful response from the orbidder /bid endpoint, i.e. the server. + * @return {Bid[]} An array of bids from orbidder. + */ interpretResponse(serverResponse) { const bidResponses = []; serverResponse = serverResponse.body; if (serverResponse && (serverResponse.length > 0)) { - serverResponse.forEach((bid) => { - const bidResponse = {}; - for (const requiredKey of ['requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency']) { - if (!bid.hasOwnProperty(requiredKey)) { - return []; + serverResponse.forEach((bidResponse) => { + if (isBidResponseValid(bidResponse)) { + if (Array.isArray(bidResponse.advertiserDomains)) { + bidResponse.meta = { + advertiserDomains: bidResponse.advertiserDomains + } } - bidResponse[requiredKey] = bid[requiredKey]; + bidResponses.push(bidResponse); } - bidResponses.push(bidResponse); }); } return bidResponses; }, }; +/** + * Get bid floor from Price Floors Module + * @param {Object} bid + * @returns {float||undefined} + */ +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.bidfloor; + } + + const floor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'EUR') { + return floor.floor; + } + return undefined; +} + registerBidder(spec); diff --git a/modules/orbidderBidAdapter.md b/modules/orbidderBidAdapter.md index c7676e6774f..6b8fac5477a 100644 --- a/modules/orbidderBidAdapter.md +++ b/modules/orbidderBidAdapter.md @@ -12,19 +12,46 @@ Module that connects to orbidder demand sources # Test Parameters ``` -var adUnits = [{ - code: '/105091519/bidder_test', - mediaTypes: { - banner: { - sizes: [728, 90] - } +var adUnits = [ + { + code: 'test_banner', + mediaTypes: { + banner: { + sizes: [728, 90] + } + }, + bids: [{ + bidder: 'orbidder', + params: { + accountId: "someAccount", + placementId: "somePlace" + } + }], }, - bids: [{ - bidder: 'orbidder' - params: { - accountId: "someAccount", - placementId: "somePlace" - } - }] -}]; + { + code: 'test_native', + mediaTypes: { + native: { + title: { + required: true, + len: 80 + }, + image: { + required: true, + sizes: [150, 50] + }, + sponsoredBy: { + required: true + } + }, + }, + bids: [{ + bidder: 'orbidder', + params: { + accountId: "someAccount", + placementId: "somePlace" + } + }], + } +]; ``` diff --git a/modules/otmBidAdapter.js b/modules/otmBidAdapter.js index 23f6d434ae1..a0e91a480a2 100644 --- a/modules/otmBidAdapter.js +++ b/modules/otmBidAdapter.js @@ -1,95 +1,146 @@ -import {BANNER} from '../src/mediaTypes.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {logInfo, logError, getBidIdParameter, _each, getValue, isFn, isPlainObject} from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'otm'; +const OTM_BID_URL = 'https://ssp.otm-r.com/adjson'; +const DEF_CUR = 'RUB' export const spec = { - code: 'otm', - supportedMediaTypes: [BANNER], + + code: BIDDER_CODE, + url: OTM_BID_URL, + supportedMediaTypes: [ BANNER ], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function (bid) { return !!bid.params.tid; }, - buildRequests: function (bidRequests) { - const requests = bidRequests.map(function (bid) { - const size = getMaxPrioritySize(bid.sizes); - const params = { - tz: getTz(), - w: size[0], - h: size[1], - s: bid.params.tid, - bidid: bid.bidId, - transactionid: bid.transactionId, - auctionid: bid.auctionId, - bidfloor: bid.params.bidfloor - }; - return {method: 'GET', url: 'https://ssp.otm-r.com/adjson', data: params} - }); + /** + * Build bidder requests. + * + * @param validBidRequests + * @param bidderRequest + * @returns {[]} + */ + buildRequests: function (validBidRequests, bidderRequest) { + logInfo('validBidRequests', validBidRequests); - return requests; - }, - interpretResponse: function (serverResponse, bidRequest) { - if (!serverResponse || !serverResponse.body) { - return []; + const bidRequests = []; + let tz = new Date().getTimezoneOffset() + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referrer = bidderRequest.refererInfo.referer; } - const answer = []; + _each(validBidRequests, (bid) => { + let domain = getValue(bid.params, 'domain') || '' + let tid = getValue(bid.params, 'tid') + let cur = getValue(bid.params, 'currency') || DEF_CUR + let bidid = getBidIdParameter('bidId', bid) + let transactionid = getBidIdParameter('transactionId', bid) + let auctionid = getBidIdParameter('auctionId', bid) + let bidfloor = _getBidFloor(bid) - serverResponse.body.forEach(bid => { - if (bid.ad) { - answer.push({ - requestId: bid.bidid, - cpm: bid.cpm, - width: bid.w, - height: bid.h, - creativeId: bid.creativeid, - currency: bid.currency || 'RUB', - netRevenue: true, - ad: bid.ad, - ttl: bid.ttl, - transactionId: bid.transactionid - }); - } - }); - - return answer; + _each(bid.sizes, size => { + let width = 0; + let height = 0; + if (size.length && typeof size[0] === 'number' && typeof size[1] === 'number') { + width = size[0]; + height = size[1]; + } + bidRequests.push({ + method: 'GET', + url: OTM_BID_URL, + data: { + tz: tz, + w: width, + h: height, + domain: domain, + l: referrer, + s: tid, + cur: cur, + bidid: bidid, + transactionid: transactionid, + auctionid: auctionid, + bidfloor: bidfloor, + }, + }) + }) + }) + return bidRequests; }, -}; -function getTz() { - return new Date().getTimezoneOffset(); -} + /** + * Generate response. + * + * @param serverResponse + * @param request + * @returns {[]|*[]} + */ + interpretResponse: function (serverResponse, request) { + logInfo('serverResponse', serverResponse.body); -function getMaxPrioritySize(sizes) { - var maxPrioritySize = null; + const responsesBody = serverResponse ? serverResponse.body : {}; + const bidResponses = []; + try { + if (responsesBody.length === 0) { + return []; + } - const sizesByPriority = [ - [300, 250], - [240, 400], - [728, 90], - [300, 600], - [970, 250], - [300, 50], - [320, 100] - ]; + _each(responsesBody, (bid) => { + if (bid.ad) { + bidResponses.push({ + requestId: bid.bidid, + cpm: bid.cpm, + width: bid.w, + height: bid.h, + creativeId: bid.creativeid, + currency: bid.currency || 'RUB', + netRevenue: true, + ad: bid.ad, + ttl: bid.ttl, + transactionId: bid.transactionid, + meta: { + advertiserDomains: bid.adDomain ? [bid.adDomain] : [] + } + }); + } + }); + } catch (error) { + logError(error); + } - const sizeToString = (size) => { - return size[0] + 'x' + size[1]; - }; + return bidResponses; + } +}; - const sizesAsString = sizes.map(sizeToString); +/** + * Get floor value + * @param bid + * @returns {null|*} + * @private + */ +function _getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.bidfloor ? bid.params.bidfloor : 0; + } - sizesByPriority.forEach(size => { - if (!maxPrioritySize) { - if (sizesAsString.indexOf(sizeToString(size)) !== -1) { - maxPrioritySize = size; - } - } + let floor = bid.getFloor({ + currency: DEF_CUR, + mediaType: '*', + size: '*' }); - - if (maxPrioritySize) { - return maxPrioritySize; - } else { - return sizes[0]; + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEF_CUR) { + return floor.floor; } + return 0; } registerBidder(spec); diff --git a/modules/otmBidAdapter.md b/modules/otmBidAdapter.md index 4962d3a8052..e5834da5729 100644 --- a/modules/otmBidAdapter.md +++ b/modules/otmBidAdapter.md @@ -1,36 +1,37 @@ # Overview -Module Name: OTM Bidder Adapter -Module Type: Bidder Adapter -Maintainer: ? +**Module Name**: OTM Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: e.kretsu@otm-r.com # Description -You can use this adapter to get a bid from otm-r.com. +OTM Bidder Adapter for Prebid.js. About: https://otm-r.com -About us : http://otm-r.com +Use `otm` as bidder: +# Params +- `tid` required, specific id AdUnit slot. +- `domain` optional, specific custom domain. +- `bidfloor` optional. -# Test Parameters -```javascript - var adUnits = [ - { - code: 'div-otm-example', - sizes: [[320, 480]], - bids: [ - { - bidder: "otm", - params: { - tid: "99", - bidfloor: 20 - } - } - ] - } - ]; +## AdUnits configuration example ``` + var adUnits = [{ + code: 'your-slot', //use exactly the same code as your slot div id. + mediaTypes: { + banner: { + sizes: [[320, 480]] + } + }, + bids: [{ + bidder: 'otm', + params: { + tid: 'XXXXX', + domain: 'specific custom domain, if needed', + bidfloor: 20 + } + }] + }]; -Where: - -* tid - A tag id (should have low cardinality) -* bidfloor - Floor price +``` diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index c0af09c2b50..439570e976e 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -5,7 +5,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { NATIVE, BANNER } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; +import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; @@ -29,7 +29,7 @@ export const spec = { isBidRequestValid: (bid) => { return ( !!config.getConfig('outbrain.bidderUrl') && - !!utils.deepAccess(bid, 'params.publisher.id') && + !!deepAccess(bid, 'params.publisher.id') && !!(bid.nativeParams || bid.sizes) ); }, @@ -40,6 +40,7 @@ export const spec = { const publisher = setOnAny(validBidRequests, 'params.publisher'); const bcat = setOnAny(validBidRequests, 'params.bcat'); const badv = setOnAny(validBidRequests, 'params.badv'); + const eids = setOnAny(validBidRequests, 'userIdAsEids') const cur = CURRENCY; const endpointUrl = config.getConfig('outbrain.bidderUrl'); const timeout = bidderRequest.timeout; @@ -79,6 +80,14 @@ export const spec = { imp: imps, bcat: bcat, badv: badv, + ext: { + prebid: { + channel: { + name: 'pbjs', + version: '$prebid.version$' + } + } + } }; if (test) { @@ -86,15 +95,19 @@ export const spec = { request.test = 1; } - if (utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { - utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString) - utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1) + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString) + deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1) } if (bidderRequest.uspConsent) { - utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent) + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent) } if (config.getConfig('coppa') === true) { - utils.deepSetValue(request, 'regs.coppa', config.getConfig('coppa') & 1) + deepSetValue(request, 'regs.coppa', config.getConfig('coppa') & 1) + } + + if (eids) { + deepSetValue(request, 'user.ext.eids', eids); } return { @@ -169,7 +182,7 @@ export const spec = { // for native requests we put the nurl as an imp tracker, otherwise if the auction takes place on prebid server // the server JS adapter puts the nurl in the adm as a tracking pixel and removes the attribute if (bid.nurl) { - ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) + ajax(replaceAuctionPrice(bid.nurl, bid.originalCpm)) } } }; @@ -208,7 +221,7 @@ function parseNative(bid) { function setOnAny(collection, key) { for (let i = 0, result; i < collection.length; i++) { - result = utils.deepAccess(collection[i], key); + result = deepAccess(collection[i], key); if (result) { return result; } @@ -220,7 +233,7 @@ function flatten(arr) { } function getNativeAssets(bid) { - return utils._map(bid.nativeParams, (bidParams, key) => { + return _map(bid.nativeParams, (bidParams, key) => { const props = NATIVE_PARAMS[key]; const asset = { required: bidParams.required & 1, @@ -258,16 +271,16 @@ function getNativeAssets(bid) { /* Turn bid request sizes into ut-compatible format */ function transformSizes(requestSizes) { - if (!utils.isArray(requestSizes)) { + if (!isArray(requestSizes)) { return []; } - if (requestSizes.length === 2 && !utils.isArray(requestSizes[0])) { + if (requestSizes.length === 2 && !isArray(requestSizes[0])) { return [{ w: parseInt(requestSizes[0], 10), h: parseInt(requestSizes[1], 10) }]; - } else if (utils.isArray(requestSizes[0])) { + } else if (isArray(requestSizes[0])) { return requestSizes.map(item => ({ w: parseInt(item[0], 10), diff --git a/modules/outconBidAdapter.js b/modules/outconBidAdapter.js deleted file mode 100644 index 0c3ac90172a..00000000000 --- a/modules/outconBidAdapter.js +++ /dev/null @@ -1,69 +0,0 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'outcon'; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: ['banner', 'video'], - isBidRequestValid: function(bid) { - return !!((bid.params.pod || (bid.params.internalId && bid.params.publisher)) && bid.params.env); - }, - buildRequests: function(validBidRequests) { - for (let i = 0; i < validBidRequests.length; i++) { - let url = ''; - let par = ''; - if (validBidRequests[i].params.pod != undefined) par = 'get?pod=' + validBidRequests[i].params.pod + '&bidId=' + validBidRequests[i].bidId; - else par = 'get?internalId=' + validBidRequests[i].params.internalId + '&publisher=' + validBidRequests[i].params.publisher + '&bidId=' + validBidRequests[i].bidId; - par = par + '&vast=true'; - switch (validBidRequests[i].params.env) { - case 'test': - par = par + '&demo=true'; - url = 'https://test.outcondigital.com/ad/' + par; - break; - case 'api': - url = 'https://api.outcondigital.com/ad/' + par; - break; - case 'stg': - url = 'https://stg.outcondigital.com/ad/' + par; - break; - } - return { - method: 'GET', - url: url, - data: {} - }; - } - }, - interpretResponse: function(serverResponse, bidRequest) { - const bidResponses = []; - const bidResponse = { - requestId: serverResponse.body.bidId, - cpm: serverResponse.body.cpm, - width: serverResponse.body.creatives[0].width, - height: serverResponse.body.creatives[0].height, - creativeId: serverResponse.body.creatives[0].id, - currency: serverResponse.body.cur, - netRevenue: true, - ttl: 300, - ad: wrapDisplayUrl(serverResponse.body.creatives[0].url, serverResponse.body.type), - vastImpUrl: serverResponse.body.trackingURL, - mediaType: serverResponse.body.type - }; - if (serverResponse.body.type == 'video') { - Object.assign(bidResponse, { - vastUrl: serverResponse.body.vastURL, - ttl: 3600 - }); - } - bidResponses.push(bidResponse); - return bidResponses; - }, -} - -function wrapDisplayUrl(displayUrl, type) { - if (type == 'video') return `
`; - if (type == 'banner') return `
`; - return null; -} - -registerBidder(spec); diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 8ada9d59ae2..3b5147907eb 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -1,26 +1,27 @@ -import * as utils from '../src/utils.js'; +import { logInfo, logError, deepAccess, logWarn, deepSetValue, isArray, contains, isStr, mergeDeep } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {getPriceBucketString} from '../src/cpmBucketManager.js'; import { Renderer } from '../src/Renderer.js'; + const BIDDER_CODE = 'ozone'; -// *** PROD *** const ORIGIN = 'https://elb.the-ozone-project.com' // applies only to auction & cookie const AUCTIONURI = '/openrtb2/auction'; const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; +const ORIGIN_DEV = 'https://test.ozpr.net'; -const OZONEVERSION = '2.5.0'; +const OZONEVERSION = '2.7.0'; export const spec = { gvlid: 524, - aliases: [{ code: 'lmc' }], + aliases: [{code: 'lmc', gvlid: 524}, {code: 'newspassid', gvlid: 524}], version: OZONEVERSION, code: BIDDER_CODE, supportedMediaTypes: [VIDEO, BANNER], - cookieSyncBag: {'publisherId': null, 'siteId': null, 'userIdObject': {}}, // variables we want to make available to cookie sync - propertyBag: {'pageId': null, 'buildRequestsStart': 0, 'buildRequestsEnd': 0}, /* allow us to store vars in instance scope - needs to be an object to be mutable */ + cookieSyncBag: {publisherId: null, siteId: null, userIdObject: {}}, // variables we want to make available to cookie sync + propertyBag: {pageId: null, buildRequestsStart: 0, buildRequestsEnd: 0, endpointOverride: null}, /* allow us to store vars in instance scope - needs to be an object to be mutable */ whitelabel_defaults: { 'logId': 'OZONE', 'bidder': 'ozone', @@ -40,19 +41,46 @@ export const spec = { this.propertyBag.whitelabel.logId = bidder.toUpperCase(); this.propertyBag.whitelabel.bidder = bidder; let bidderConfig = config.getConfig(bidder) || {}; + logInfo('got bidderConfig: ', JSON.parse(JSON.stringify(bidderConfig))); if (bidderConfig.kvpPrefix) { this.propertyBag.whitelabel.keyPrefix = bidderConfig.kvpPrefix; } + let arr = this.getGetParametersAsObject(); if (bidderConfig.endpointOverride) { if (bidderConfig.endpointOverride.origin) { + this.propertyBag.endpointOverride = bidderConfig.endpointOverride.origin; this.propertyBag.whitelabel.auctionUrl = bidderConfig.endpointOverride.origin + AUCTIONURI; this.propertyBag.whitelabel.cookieSyncUrl = bidderConfig.endpointOverride.origin + OZONECOOKIESYNC; } - if (bidderConfig.endpointOverride.rendererUrl) { + + if (arr.hasOwnProperty('renderer')) { + if (arr.renderer.match('%3A%2F%2F')) { + this.propertyBag.whitelabel.rendererUrl = decodeURIComponent(arr['renderer']); + } else { + this.propertyBag.whitelabel.rendererUrl = arr['renderer']; + } + } else if (bidderConfig.endpointOverride.rendererUrl) { this.propertyBag.whitelabel.rendererUrl = bidderConfig.endpointOverride.rendererUrl; } + if (bidderConfig.endpointOverride.cookieSyncUrl) { + this.propertyBag.whitelabel.cookieSyncUrl = bidderConfig.endpointOverride.cookieSyncUrl; + } + if (bidderConfig.endpointOverride.auctionUrl) { + this.propertyBag.endpointOverride = bidderConfig.endpointOverride.auctionUrl; + this.propertyBag.whitelabel.auctionUrl = bidderConfig.endpointOverride.auctionUrl; + } } - this.logInfo('set propertyBag.whitelabel to', this.propertyBag.whitelabel); + try { + if (arr.hasOwnProperty('auction') && arr.auction === 'dev') { + logInfo('GET: auction=dev'); + this.propertyBag.whitelabel.auctionUrl = ORIGIN_DEV + AUCTIONURI; + } + if (arr.hasOwnProperty('cookiesync') && arr.cookiesync === 'dev') { + logInfo('GET: cookiesync=dev'); + this.propertyBag.whitelabel.cookieSyncUrl = ORIGIN_DEV + OZONECOOKIESYNC; + } + } catch (e) {} + logInfo('set propertyBag.whitelabel to', this.propertyBag.whitelabel); }, getAuctionUrl() { return this.propertyBag.whitelabel.auctionUrl; @@ -63,27 +91,6 @@ export const spec = { getRendererUrl() { return this.propertyBag.whitelabel.rendererUrl; }, - /** - * wrappers for this.logInfo logWarn & logError, to add the proper prefix - */ - logInfo() { - if (!this.propertyBag.whitelabel) { return; } - let args = arguments; - args[0] = `${this.propertyBag.whitelabel.logId}: ${arguments[0]}`; - utils.logInfo.apply(this, args); - }, - logError() { - if (!this.propertyBag.whitelabel) { return; } - let args = arguments; - args[0] = `${this.propertyBag.whitelabel.logId}: ${arguments[0]}`; - utils.logError.apply(this, args); - }, - logWarn() { - if (!this.propertyBag.whitelabel) { return; } - let args = arguments; - args[0] = `${this.propertyBag.whitelabel.logId}: ${arguments[0]}`; - utils.logWarn.apply(this, args); - }, /** * Basic check to see whether required parameters are in the request. * @param bid @@ -91,62 +98,62 @@ export const spec = { */ isBidRequestValid(bid) { this.loadWhitelabelData(bid); - this.logInfo('isBidRequestValid : ', config.getConfig(), bid); + logInfo('isBidRequestValid : ', config.getConfig(), bid); let adUnitCode = bid.adUnitCode; // adunit[n].code - + let err1 = 'VALIDATION FAILED : missing {param} : siteId, placementId and publisherId are REQUIRED' if (!(bid.params.hasOwnProperty('placementId'))) { - this.logError('BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + logError(err1.replace('{param}', 'placementId'), adUnitCode); return false; } if (!this.isValidPlacementId(bid.params.placementId)) { - this.logError('BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); + logError('VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); return false; } if (!(bid.params.hasOwnProperty('publisherId'))) { - this.logError('BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + logError(err1.replace('{param}', 'publisherId'), adUnitCode); return false; } if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { - this.logError('BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens', adUnitCode); + logError('VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens', adUnitCode); return false; } if (!(bid.params.hasOwnProperty('siteId'))) { - this.logError('BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + logError(err1.replace('{param}', 'siteId'), adUnitCode); return false; } if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { - this.logError('BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); + logError('VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); return false; } if (bid.params.hasOwnProperty('customParams')) { - this.logError('BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); + logError('VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); return false; } if (bid.params.hasOwnProperty('customData')) { if (!Array.isArray(bid.params.customData)) { - this.logError('BID ADAPTER VALIDATION FAILED : customData is not an Array', adUnitCode); + logError('VALIDATION FAILED : customData is not an Array', adUnitCode); return false; } if (bid.params.customData.length < 1) { - this.logError('BID ADAPTER VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); + logError('VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); return false; } if (!(bid.params.customData[0]).hasOwnProperty('targeting')) { - this.logError('BID ADAPTER VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); + logError('VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); return false; } if (typeof bid.params.customData[0]['targeting'] != 'object') { - this.logError('BID ADAPTER VALIDATION FAILED : customData[0] targeting is not an object', adUnitCode); + logError('VALIDATION FAILED : customData[0] targeting is not an object', adUnitCode); return false; } } if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { - this.logError('No video context key/value in bid. Rejecting bid: ', bid); + logError('No video context key/value in bid. Rejecting bid: ', bid); return false; } if (bid.mediaTypes[VIDEO].context !== 'instream' && bid.mediaTypes[VIDEO].context !== 'outstream') { - this.logError('video.context is invalid. Only instream/outstream video is supported. Rejecting bid: ', bid); + logError('video.context is invalid. Only instream/outstream video is supported. Rejecting bid: ', bid); return false; } } @@ -166,97 +173,91 @@ export const spec = { this.propertyBag.buildRequestsStart = new Date().getTime(); let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; - this.logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${OZONEVERSION} validBidRequests`, JSON.parse(JSON.stringify(validBidRequests)), 'bidderRequest', JSON.parse(JSON.stringify(bidderRequest))); - // First check - is there any config to block this request? + logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${OZONEVERSION} validBidRequests`, JSON.parse(JSON.stringify(validBidRequests)), 'bidderRequest', JSON.parse(JSON.stringify(bidderRequest))); if (this.blockTheRequest()) { return []; } let htmlParams = {'publisherId': '', 'siteId': ''}; if (validBidRequests.length > 0) { this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIds(validBidRequests[0])); - this.cookieSyncBag.siteId = utils.deepAccess(validBidRequests[0], 'params.siteId'); - this.cookieSyncBag.publisherId = utils.deepAccess(validBidRequests[0], 'params.publisherId'); + this.cookieSyncBag.siteId = deepAccess(validBidRequests[0], 'params.siteId'); + this.cookieSyncBag.publisherId = deepAccess(validBidRequests[0], 'params.publisherId'); htmlParams = validBidRequests[0].params; } - this.logInfo('cookie sync bag', this.cookieSyncBag); + logInfo('cookie sync bag', this.cookieSyncBag); let singleRequest = this.getWhitelabelConfigItem('ozone.singleRequest'); singleRequest = singleRequest !== false; // undefined & true will be true - this.logInfo(`config ${whitelabelBidder}.singleRequest : `, singleRequest); + logInfo(`config ${whitelabelBidder}.singleRequest : `, singleRequest); let ozoneRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params delete ozoneRequest.test; // don't allow test to be set in the config - ONLY use $_GET['pbjs_debug'] - if (bidderRequest && bidderRequest.gdprConsent) { - this.logInfo('ADDING GDPR info'); - let apiVersion = bidderRequest.gdprConsent.apiVersion || '1'; - ozoneRequest.regs = {ext: {gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, apiVersion: apiVersion}}; - if (ozoneRequest.regs.ext.gdpr) { - ozoneRequest.user = ozoneRequest.user || {}; - ozoneRequest.user.ext = {'consent': bidderRequest.gdprConsent.consentString}; - } else { - this.logInfo('**** Strange CMP info: bidderRequest.gdprConsent exists BUT bidderRequest.gdprConsent.gdprApplies is false. See bidderRequest logged above. ****'); - } - } else { - this.logInfo('WILL NOT ADD GDPR info; no bidderRequest.gdprConsent object was present.'); + let fpd = config.getConfig('ortb2'); + if (fpd && deepAccess(fpd, 'user')) { + logInfo('added FPD user object'); + ozoneRequest.user = fpd.user; } + const getParams = this.getGetParametersAsObject(); const wlOztestmodeKey = whitelabelPrefix + 'testmode'; const isTestMode = getParams[wlOztestmodeKey] || null; // this can be any string, it's used for testing ads ozoneRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string - // build the array of params to attach to `imp` + let schain = null; let tosendtags = validBidRequests.map(ozoneBidRequest => { var obj = {}; let placementId = placementIdOverrideFromGetParam || this.getPlacementId(ozoneBidRequest); // prefer to use a valid override param, else the bidRequest placement Id obj.id = ozoneBidRequest.bidId; // this causes an error if we change it to something else, even if you update the bidRequest object: "WARNING: Bidder ozone made bid for unknown request ID: mb7953.859498327448. Ignoring." obj.tagid = placementId; obj.secure = window.location.protocol === 'https:' ? 1 : 0; - // is there a banner (or nothing declared, so banner is the default)? let arrBannerSizes = []; if (!ozoneBidRequest.hasOwnProperty('mediaTypes')) { if (ozoneBidRequest.hasOwnProperty('sizes')) { - this.logInfo('no mediaTypes detected - will use the sizes array in the config root'); + logInfo('no mediaTypes detected - will use the sizes array in the config root'); arrBannerSizes = ozoneBidRequest.sizes; } else { - this.logInfo('no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); + logInfo('no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); } } else { if (ozoneBidRequest.mediaTypes.hasOwnProperty(BANNER)) { arrBannerSizes = ozoneBidRequest.mediaTypes[BANNER].sizes; /* Note - if there is a sizes element in the config root it will be pushed into here */ - this.logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); + logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); } if (ozoneBidRequest.mediaTypes.hasOwnProperty(VIDEO)) { - this.logInfo('openrtb 2.5 compliant video'); - // examine all the video attributes in the config, and either put them into obj.video if allowed by IAB2.5 or else in to obj.video.ext + logInfo('openrtb 2.5 compliant video'); if (typeof ozoneBidRequest.mediaTypes[VIDEO] == 'object') { - let childConfig = utils.deepAccess(ozoneBidRequest, 'params.video', {}); + let childConfig = deepAccess(ozoneBidRequest, 'params.video', {}); obj.video = this.unpackVideoConfigIntoIABformat(ozoneBidRequest.mediaTypes[VIDEO], childConfig); obj.video = this.addVideoDefaults(obj.video, ozoneBidRequest.mediaTypes[VIDEO], childConfig); } - // we need to duplicate some of the video values let wh = getWidthAndHeightFromVideoObject(obj.video); - this.logInfo('setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video, 'wh=', wh); + logInfo('setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video, 'wh=', wh); if (wh && typeof wh === 'object') { obj.video.w = wh['w']; obj.video.h = wh['h']; if (playerSizeIsNestedArray(obj.video)) { // this should never happen; it was in the original spec for this change though. - this.logInfo('setting obj.video.format to be an array of objects'); + logInfo('setting obj.video.format to be an array of objects'); obj.video.ext.format = [wh]; } else { - this.logInfo('setting obj.video.format to be an object'); + logInfo('setting obj.video.format to be an object'); obj.video.ext.format = wh; } } else { - this.logWarn('cannot set w, h & format values for video; the config is not right'); + logWarn('cannot set w, h & format values for video; the config is not right'); } } - // Native integration is not complete yet if (ozoneBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { obj.native = ozoneBidRequest.mediaTypes[NATIVE]; - this.logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); + logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); + } + if (ozoneBidRequest.hasOwnProperty('getFloor')) { + logInfo('This bidRequest object has property: getFloor'); + obj.floor = this.getFloorObjectForAuction(ozoneBidRequest); + logInfo('obj.floor is : ', obj.floor); + } else { + logInfo('This bidRequest object DOES NOT have property: getFloor'); } } if (arrBannerSizes.length > 0) { - // build the banner request using banner sizes we found in either possible location: obj.banner = { topframe: 1, w: arrBannerSizes[0][0] || 0, @@ -266,19 +267,17 @@ export const spec = { }) }; } - // these 3 MUST exist - we check them in the validation method obj.placementId = placementId; - // build the imp['ext'] object - obj.ext = {'prebid': {'storedrequest': {'id': placementId}}}; + deepSetValue(obj, 'ext.prebid', {'storedrequest': {'id': placementId}}); obj.ext[whitelabelBidder] = {}; obj.ext[whitelabelBidder].adUnitCode = ozoneBidRequest.adUnitCode; // eg. 'mpu' obj.ext[whitelabelBidder].transactionId = ozoneBidRequest.transactionId; // this is the transactionId PER adUnit, common across bidders for this unit if (ozoneBidRequest.params.hasOwnProperty('customData')) { obj.ext[whitelabelBidder].customData = ozoneBidRequest.params.customData; } - this.logInfo(`obj.ext.${whitelabelBidder} is `, obj.ext[whitelabelBidder]); + logInfo(`obj.ext.${whitelabelBidder} is `, obj.ext[whitelabelBidder]); if (isTestMode != null) { - this.logInfo('setting isTestMode to ', isTestMode); + logInfo('setting isTestMode to ', isTestMode); if (obj.ext[whitelabelBidder].hasOwnProperty('customData')) { for (let i = 0; i < obj.ext[whitelabelBidder].customData.length; i++) { obj.ext[whitelabelBidder].customData[i]['targeting'][wlOztestmodeKey] = isTestMode; @@ -288,36 +287,51 @@ export const spec = { obj.ext[whitelabelBidder].customData[0].targeting[wlOztestmodeKey] = isTestMode; } } + if (fpd && deepAccess(fpd, 'site')) { + logInfo('added fpd.site'); + if (deepAccess(obj, 'ext.' + whitelabelBidder + '.customData.0.targeting', false)) { + obj.ext[whitelabelBidder].customData[0].targeting = Object.assign(obj.ext[whitelabelBidder].customData[0].targeting, fpd.site); + } else { + deepSetValue(obj, 'ext.' + whitelabelBidder + '.customData.0.targeting', fpd.site); + } + } + if (!schain && deepAccess(ozoneBidRequest, 'schain')) { + schain = ozoneBidRequest.schain; + } return obj; }); - // in v 2.0.0 we moved these outside of the individual ad slots let extObj = {}; extObj[whitelabelBidder] = {}; extObj[whitelabelBidder][whitelabelPrefix + '_pb_v'] = OZONEVERSION; extObj[whitelabelBidder][whitelabelPrefix + '_rw'] = placementIdOverrideFromGetParam ? 1 : 0; if (validBidRequests.length > 0) { let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info - // let userIds = this.findAllUserIds(validBidRequests[0]); if (userIds.hasOwnProperty('pubcid')) { extObj[whitelabelBidder].pubcid = userIds.pubcid; } } + extObj[whitelabelBidder].pv = this.getPageId(); // attach the page ID that will be common to all auciton calls for this page if refresh() is called let ozOmpFloorDollars = this.getWhitelabelConfigItem('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') - this.logInfo(`${whitelabelPrefix}_omp_floor dollar value = `, ozOmpFloorDollars); + logInfo(`${whitelabelPrefix}_omp_floor dollar value = `, ozOmpFloorDollars); if (typeof ozOmpFloorDollars === 'number') { extObj[whitelabelBidder][whitelabelPrefix + '_omp_floor'] = ozOmpFloorDollars; } else if (typeof ozOmpFloorDollars !== 'undefined') { - this.logError(`${whitelabelPrefix}_omp_floor is invalid - IF SET then this must be a number, representing dollar value eg. ${whitelabelPrefix}_omp_floor: 1.55. You have it set as a ` + (typeof ozOmpFloorDollars)); + logError(`${whitelabelPrefix}_omp_floor is invalid - IF SET then this must be a number, representing dollar value eg. ${whitelabelPrefix}_omp_floor: 1.55. You have it set as a ` + (typeof ozOmpFloorDollars)); } let ozWhitelistAdserverKeys = this.getWhitelabelConfigItem('ozone.oz_whitelist_adserver_keys'); - let useOzWhitelistAdserverKeys = utils.isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; + let useOzWhitelistAdserverKeys = isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; extObj[whitelabelBidder][whitelabelPrefix + '_kvp_rw'] = useOzWhitelistAdserverKeys ? 1 : 0; if (whitelabelBidder != 'ozone') { - this.logInfo('setting aliases object'); + logInfo('setting aliases object'); extObj.prebid = {aliases: {'ozone': whitelabelBidder}}; } + if (getParams.hasOwnProperty('ozf')) { extObj[whitelabelBidder]['ozf'] = getParams.ozf == 'true' || getParams.ozf == 1 ? 1 : 0; } + if (getParams.hasOwnProperty('ozpf')) { extObj[whitelabelBidder]['ozpf'] = getParams.ozpf == 'true' || getParams.ozpf == 1 ? 1 : 0; } + if (getParams.hasOwnProperty('ozrp') && getParams.ozrp.match(/^[0-3]$/)) { extObj[whitelabelBidder]['ozrp'] = parseInt(getParams.ozrp); } + if (getParams.hasOwnProperty('ozip') && getParams.ozip.match(/^\d+$/)) { extObj[whitelabelBidder]['ozip'] = parseInt(getParams.ozip); } + if (this.propertyBag.endpointOverride != null) { extObj[whitelabelBidder]['origin'] = this.propertyBag.endpointOverride; } var userExtEids = this.generateEids(validBidRequests); // generate the UserIDs in the correct format for UserId module @@ -328,44 +342,63 @@ export const spec = { }; ozoneRequest.test = (getParams.hasOwnProperty('pbjs_debug') && getParams['pbjs_debug'] === 'true') ? 1 : 0; - // this is for 2.2.1 - // coppa compliance + if (bidderRequest && bidderRequest.gdprConsent) { + logInfo('ADDING GDPR info'); + let apiVersion = deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1); + ozoneRequest.regs = {ext: {gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, apiVersion: apiVersion}}; + if (deepAccess(ozoneRequest, 'regs.ext.gdpr')) { + deepSetValue(ozoneRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } else { + logInfo('**** Strange CMP info: bidderRequest.gdprConsent exists BUT bidderRequest.gdprConsent.gdprApplies is false. See bidderRequest logged above. ****'); + } + } else { + logInfo('WILL NOT ADD GDPR info; no bidderRequest.gdprConsent object'); + } + if (bidderRequest && bidderRequest.uspConsent) { + logInfo('ADDING CCPA info'); + deepSetValue(ozoneRequest, 'user.ext.uspConsent', bidderRequest.uspConsent); + } else { + logInfo('WILL NOT ADD CCPA info; no bidderRequest.uspConsent.'); + } + if (schain) { // we set this while iterating over the bids + logInfo('schain found'); + deepSetValue(ozoneRequest, 'source.ext.schain', schain); + } + if (config.getConfig('coppa') === true) { - utils.deepSetValue(ozoneRequest, 'regs.coppa', 1); + deepSetValue(ozoneRequest, 'regs.coppa', 1); } - // return the single request object OR the array: if (singleRequest) { - this.logInfo('buildRequests starting to generate response for a single request'); + logInfo('buildRequests starting to generate response for a single request'); ozoneRequest.id = bidderRequest.auctionId; // Unique ID of the bid request, provided by the exchange. ozoneRequest.auctionId = bidderRequest.auctionId; // not sure if this should be here? ozoneRequest.imp = tosendtags; ozoneRequest.ext = extObj; - ozoneRequest.source = {'tid': bidderRequest.auctionId}; // RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). - utils.deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); + deepSetValue(ozoneRequest, 'source.tid', bidderRequest.auctionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). + deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); var ret = { method: 'POST', url: this.getAuctionUrl(), data: JSON.stringify(ozoneRequest), bidderRequest: bidderRequest }; - this.logInfo('buildRequests request data for single = ', ozoneRequest); + logInfo('buildRequests request data for single = ', JSON.parse(JSON.stringify(ozoneRequest))); this.propertyBag.buildRequestsEnd = new Date().getTime(); - this.logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); + logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); return ret; } - // not single request - pull apart the tosendtags array & return an array of objects each containing one element in the imp array. let arrRet = tosendtags.map(imp => { - this.logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); + logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); let ozoneRequestSingle = Object.assign({}, ozoneRequest); imp.ext[whitelabelBidder].pageAuctionId = bidderRequest['auctionId']; // make a note in the ext object of what the original auctionId was, in the bidderRequest object ozoneRequestSingle.id = imp.ext[whitelabelBidder].transactionId; // Unique ID of the bid request, provided by the exchange. ozoneRequestSingle.auctionId = imp.ext[whitelabelBidder].transactionId; // not sure if this should be here? ozoneRequestSingle.imp = [imp]; ozoneRequestSingle.ext = extObj; - ozoneRequestSingle.source = {'tid': imp.ext[whitelabelBidder].transactionId}; - utils.deepSetValue(ozoneRequestSingle, 'user.ext.eids', userExtEids); - this.logInfo('buildRequests RequestSingle (for non-single) = ', ozoneRequestSingle); + deepSetValue(ozoneRequestSingle, 'source.tid', imp.ext[whitelabelBidder].transactionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). + deepSetValue(ozoneRequestSingle, 'user.ext.eids', userExtEids); + logInfo('buildRequests RequestSingle (for non-single) = ', ozoneRequestSingle); return { method: 'POST', url: this.getAuctionUrl(), @@ -374,9 +407,42 @@ export const spec = { }; }); this.propertyBag.buildRequestsEnd = new Date().getTime(); - this.logInfo(`buildRequests going to return for non-single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, arrRet); + logInfo(`buildRequests going to return for non-single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, arrRet); return arrRet; }, + /** + * parse a bidRequestRef that contains getFloor(), get all the data from it for the sizes & media requested for this bid & return an object containing floor data you can send to auciton endpoint + * @param bidRequestRef object = a valid bid request object reference + * @return object + * + * call: + * bidObj.getFloor({ + currency: 'USD', <- currency to return the value in + mediaType: ‘banner’, + size: ‘*’ <- or [300,250] or [[300,250],[640,480]] + * }); + * + */ + getFloorObjectForAuction(bidRequestRef) { + const mediaTypesSizes = { + banner: deepAccess(bidRequestRef, 'mediaTypes.banner.sizes', null), + video: deepAccess(bidRequestRef, 'mediaTypes.video.playerSize', null), + native: deepAccess(bidRequestRef, 'mediaTypes.native.image.sizes', null) + } + logInfo('getFloorObjectForAuction mediaTypesSizes : ', mediaTypesSizes); + let ret = {}; + if (mediaTypesSizes.banner) { + ret.banner = bidRequestRef.getFloor({mediaType: 'banner', currency: 'USD', size: mediaTypesSizes.banner}); + } + if (mediaTypesSizes.video) { + ret.video = bidRequestRef.getFloor({mediaType: 'video', currency: 'USD', size: mediaTypesSizes.video}); + } + if (mediaTypesSizes.native) { + ret.native = bidRequestRef.getFloor({mediaType: 'native', currency: 'USD', size: mediaTypesSizes.native}); + } + logInfo('getFloorObjectForAuction returning : ', JSON.parse(JSON.stringify(ret))); + return ret; + }, /** * Interpret the response if the array contains BIDDER elements, in the format: [ [bidder1 bid 1, bidder1 bid 2], [bidder2 bid 1, bidder2 bid 2] ] * NOte that in singleRequest mode this will be called once, else it will be called for each adSlot's response @@ -392,10 +458,9 @@ export const spec = { let startTime = new Date().getTime(); let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; - this.logInfo(`interpretResponse time: ${startTime} . Time between buildRequests done and interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); - this.logInfo(`serverResponse, request`, JSON.parse(JSON.stringify(serverResponse)), JSON.parse(JSON.stringify(request))); + logInfo(`interpretResponse time: ${startTime} . Time between buildRequests done and interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); + logInfo(`serverResponse, request`, JSON.parse(JSON.stringify(serverResponse)), JSON.parse(JSON.stringify(request))); serverResponse = serverResponse.body || {}; - // note that serverResponse.id value is the auction_id we might want to use for reporting reasons. if (!serverResponse.hasOwnProperty('seatbid')) { return []; } @@ -404,47 +469,48 @@ export const spec = { } let arrAllBids = []; let enhancedAdserverTargeting = this.getWhitelabelConfigItem('ozone.enhancedAdserverTargeting'); - this.logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); + logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); if (typeof enhancedAdserverTargeting == 'undefined') { enhancedAdserverTargeting = true; } - this.logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); + logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); + serverResponse.seatbid = injectAdIdsIntoAllBidResponses(serverResponse.seatbid); // we now make sure that each bid in the bidresponse has a unique (within page) adId attribute. + serverResponse.seatbid = this.removeSingleBidderMultipleBids(serverResponse.seatbid); let ozOmpFloorDollars = this.getWhitelabelConfigItem('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') let addOzOmpFloorDollars = typeof ozOmpFloorDollars === 'number'; let ozWhitelistAdserverKeys = this.getWhitelabelConfigItem('ozone.oz_whitelist_adserver_keys'); - let useOzWhitelistAdserverKeys = utils.isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; + let useOzWhitelistAdserverKeys = isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; for (let i = 0; i < serverResponse.seatbid.length; i++) { let sb = serverResponse.seatbid[i]; for (let j = 0; j < sb.bid.length; j++) { let thisRequestBid = this.getBidRequestForBidId(sb.bid[j].impid, request.bidderRequest.bids); - this.logInfo(`seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); + logInfo(`seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); const {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); let thisBid = ozoneAddStandardProperties(sb.bid[j], defaultWidth, defaultHeight); + thisBid.meta = {advertiserDomains: thisBid.adomain || []}; let videoContext = null; let isVideo = false; - let bidType = utils.deepAccess(thisBid, 'ext.prebid.type'); - this.logInfo(`this bid type is : ${bidType}`, j); + let bidType = deepAccess(thisBid, 'ext.prebid.type'); + logInfo(`this bid type is : ${bidType}`, j); if (bidType === VIDEO) { isVideo = true; videoContext = this.getVideoContextForBidId(thisBid.bidId, request.bidderRequest.bids); // should be instream or outstream (or null if error) if (videoContext === 'outstream') { - this.logInfo('going to attach a renderer to OUTSTREAM video : ', j); + logInfo('going to attach a renderer to OUTSTREAM video : ', j); thisBid.renderer = newRenderer(thisBid.bidId); } else { - this.logInfo('bid is not an outstream video, will not attach a renderer: ', j); + logInfo('bid is not an outstream video, will not attach a renderer: ', j); } } let adserverTargeting = {}; if (enhancedAdserverTargeting) { let allBidsForThisBidid = ozoneGetAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); - // add all the winning & non-winning bids for this bidId: - this.logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); + logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); Object.keys(allBidsForThisBidid).forEach((bidderName, index, ar2) => { - this.logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); - // let bidderName = bidderNameWH.split('_')[0]; + logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); adserverTargeting[whitelabelPrefix + '_' + bidderName] = bidderName; adserverTargeting[whitelabelPrefix + '_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); adserverTargeting[whitelabelPrefix + '_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); @@ -459,11 +525,11 @@ export const spec = { if (isVideo) { adserverTargeting[whitelabelPrefix + '_' + bidderName + '_vid'] = videoContext; // outstream or instream } - let flr = utils.deepAccess(allBidsForThisBidid[bidderName], `ext.bidder.${whitelabelBidder}.floor`, null); + let flr = deepAccess(allBidsForThisBidid[bidderName], `ext.bidder.${whitelabelBidder}.floor`, null); if (flr != null) { adserverTargeting[whitelabelPrefix + '_' + bidderName + '_flr'] = flr; } - let rid = utils.deepAccess(allBidsForThisBidid[bidderName], `ext.bidder.${whitelabelBidder}.ruleId`, null); + let rid = deepAccess(allBidsForThisBidid[bidderName], `ext.bidder.${whitelabelBidder}.ruleId`, null); if (rid != null) { adserverTargeting[whitelabelPrefix + '_' + bidderName + '_rid'] = rid; } @@ -473,21 +539,26 @@ export const spec = { }); } else { if (useOzWhitelistAdserverKeys) { - this.logWarn(`You have set a whitelist of adserver keys but this will be ignored because ${whitelabelBidder}.enhancedAdserverTargeting is set to false. No per-bid keys will be sent to adserver.`); + logWarn(`You have set a whitelist of adserver keys but this will be ignored because ${whitelabelBidder}.enhancedAdserverTargeting is set to false. No per-bid keys will be sent to adserver.`); } else { - this.logInfo(`${whitelabelBidder}.enhancedAdserverTargeting is set to false, so no per-bid keys will be sent to adserver.`); + logInfo(`${whitelabelBidder}.enhancedAdserverTargeting is set to false, so no per-bid keys will be sent to adserver.`); } } - // also add in the winning bid, to be sent to dfp let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); adserverTargeting[whitelabelPrefix + '_auc_id'] = String(request.bidderRequest.auctionId); adserverTargeting[whitelabelPrefix + '_winner'] = String(winningSeat); + adserverTargeting[whitelabelPrefix + '_bid'] = 'true'; + if (enhancedAdserverTargeting) { adserverTargeting[whitelabelPrefix + '_imp_id'] = String(winningBid.impid); adserverTargeting[whitelabelPrefix + '_pb_v'] = OZONEVERSION; + adserverTargeting[whitelabelPrefix + '_pb'] = winningBid.price; + adserverTargeting[whitelabelPrefix + '_pb_r'] = getRoundedBid(winningBid.price, bidType); + adserverTargeting[whitelabelPrefix + '_adId'] = String(winningBid.adId); + adserverTargeting[whitelabelPrefix + '_size'] = `${winningBid.width}x${winningBid.height}`; } if (useOzWhitelistAdserverKeys) { // delete any un-whitelisted keys - this.logInfo('Going to filter out adserver targeting keys not in the whitelist: ', ozWhitelistAdserverKeys); + logInfo('Going to filter out adserver targeting keys not in the whitelist: ', ozWhitelistAdserverKeys); Object.keys(adserverTargeting).forEach(function(key) { if (ozWhitelistAdserverKeys.indexOf(key) === -1) { delete adserverTargeting[key]; } }); } thisBid.adserverTargeting = adserverTargeting; @@ -495,20 +566,20 @@ export const spec = { } } let endTime = new Date().getTime(); - this.logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`, arrAllBids); + logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`, arrAllBids); return arrAllBids; }, /** * Use this to get all config values * Now it's getting complicated with whitelabeling, this simplifies the code for getting config values. - * eg. to get ozone.oz_omp_floor you just send '_omp_floor' + * eg. to get whitelabelled version you just sent the ozone default string like ozone.oz_omp_floor * @param ozoneVersion string like 'ozone.oz_omp_floor' * @return {string|object} */ getWhitelabelConfigItem(ozoneVersion) { if (this.propertyBag.whitelabel.bidder == 'ozone') { return config.getConfig(ozoneVersion); } let whitelabelledSearch = ozoneVersion.replace('ozone', this.propertyBag.whitelabel.bidder); - whitelabelledSearch = ozoneVersion.replace('oz_', this.propertyBag.whitelabel.keyPrefix + '_'); + whitelabelledSearch = whitelabelledSearch.replace('oz_', this.propertyBag.whitelabel.keyPrefix + '_'); return config.getConfig(whitelabelledSearch); }, /** @@ -523,7 +594,7 @@ export const spec = { var bidIds = []; for (let j = 0; j < sb.bid.length; j++) { var candidate = sb.bid[j]; - if (utils.contains(bidIds, candidate.impid)) { + if (contains(bidIds, candidate.impid)) { continue; // we've already fully assessed this impid, found the highest bid from this seat for it } bidIds.push(candidate.impid); @@ -538,9 +609,8 @@ export const spec = { } return ret; }, - // see http://prebid.org/dev-docs/bidder-adaptor.html#registering-user-syncs - getUserSyncs(optionsType, serverResponse, gdprConsent) { - this.logInfo('getUserSyncs optionsType, serverResponse, gdprConsent, cookieSyncBag', optionsType, serverResponse, gdprConsent, this.cookieSyncBag); + getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy) { + logInfo('getUserSyncs optionsType', optionsType, 'serverResponse', serverResponse, 'gdprConsent', gdprConsent, 'usPrivacy', usPrivacy, 'cookieSyncBag', this.cookieSyncBag); if (!serverResponse || serverResponse.length === 0) { return []; } @@ -549,11 +619,10 @@ export const spec = { if (document.location.search.match(/pbjs_debug=true/)) { arrQueryString.push('pbjs_debug=true'); } - arrQueryString.push('gdpr=' + (utils.deepAccess(gdprConsent, 'gdprApplies', false) ? '1' : '0')); - arrQueryString.push('gdpr_consent=' + utils.deepAccess(gdprConsent, 'consentString', '')); - var objKeys = Object.getOwnPropertyNames(this.cookieSyncBag.userIdObject); - for (let idx in objKeys) { - let keyname = objKeys[idx]; + arrQueryString.push('gdpr=' + (deepAccess(gdprConsent, 'gdprApplies', false) ? '1' : '0')); + arrQueryString.push('gdpr_consent=' + deepAccess(gdprConsent, 'consentString', '')); + arrQueryString.push('usp_consent=' + (usPrivacy || '')); + for (let keyname in this.cookieSyncBag.userIdObject) { arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); } arrQueryString.push('publisherId=' + this.cookieSyncBag.publisherId); @@ -565,7 +634,7 @@ export const spec = { if (strQueryString.length > 0) { strQueryString = '?' + strQueryString; } - this.logInfo('getUserSyncs going to return cookie sync url : ' + this.getCookieSyncUrl() + strQueryString); + logInfo('getUserSyncs going to return cookie sync url : ' + this.getCookieSyncUrl() + strQueryString); return [{ type: 'iframe', url: this.getCookieSyncUrl() + strQueryString @@ -595,20 +664,18 @@ export const spec = { getVideoContextForBidId(bidId, arrBids) { let requestBid = this.getBidRequestForBidId(bidId, arrBids); if (requestBid != null) { - return utils.deepAccess(requestBid, 'mediaTypes.video.context', 'unknown') + return deepAccess(requestBid, 'mediaTypes.video.context', 'unknown') } return null; }, /** + * This is used for cookie sync, not auction call * Look for pubcid & all the other IDs according to http://prebid.org/dev-docs/modules/userId.html - * NOTE that criteortus is deprecated & should be removed asap * @return map */ findAllUserIds(bidRequest) { var ret = {}; - // @todo - what is fabrick called & where to look for it? If it's a simple value then it will automatically be ok - let searchKeysSingle = ['pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'criteoId', 'criteortus', - 'sharedid', 'lotamePanoramaId', 'fabrickId']; + let searchKeysSingle = ['pubcid', 'tdid', 'idl_env', 'criteoId', 'lotamePanoramaId', 'fabrickId']; if (bidRequest.hasOwnProperty('userId')) { for (let arrayId in searchKeysSingle) { let key = searchKeysSingle[arrayId]; @@ -616,19 +683,36 @@ export const spec = { if (typeof (bidRequest.userId[key]) == 'string') { ret[key] = bidRequest.userId[key]; } else if (typeof (bidRequest.userId[key]) == 'object') { + logError(`WARNING: findAllUserIds had to use first key in user object to get value for bid.userId key: ${key}. Prebid adapter should be updated.`); ret[key] = bidRequest.userId[key][Object.keys(bidRequest.userId[key])[0]]; // cannot use Object.values } else { - this.logError(`failed to get string key value for userId : ${key}`); + logError(`failed to get string key value for userId : ${key}`); } } } - var lipbid = utils.deepAccess(bidRequest.userId, 'lipb.lipbid'); + let lipbid = deepAccess(bidRequest.userId, 'lipb.lipbid'); if (lipbid) { ret['lipb'] = {'lipbid': lipbid}; } + let id5id = deepAccess(bidRequest.userId, 'id5id.uid'); + if (id5id) { + ret['id5id'] = id5id; + } + let parrableId = deepAccess(bidRequest.userId, 'parrableId.eid'); + if (parrableId) { + ret['parrableId'] = parrableId; + } + let sharedid = deepAccess(bidRequest.userId, 'sharedid.id'); + if (sharedid) { + ret['sharedid'] = sharedid; + } + let sharedidthird = deepAccess(bidRequest.userId, 'sharedid.third'); + if (sharedidthird) { + ret['sharedidthird'] = sharedidthird; + } } if (!ret.hasOwnProperty('pubcid')) { - var pubcid = utils.deepAccess(bidRequest, 'crumbs.pubcid'); + let pubcid = deepAccess(bidRequest, 'crumbs.pubcid'); if (pubcid) { ret['pubcid'] = pubcid; // if built with old pubCommonId module } @@ -654,10 +738,10 @@ export const spec = { let arr = this.getGetParametersAsObject(); if (arr.hasOwnProperty(whitelabelPrefix + 'storedrequest')) { if (this.isValidPlacementId(arr[whitelabelPrefix + 'storedrequest'])) { - this.logInfo(`using GET ${whitelabelPrefix}storedrequest ` + arr[whitelabelPrefix + 'storedrequest'] + ' to replace placementId'); + logInfo(`using GET ${whitelabelPrefix}storedrequest ` + arr[whitelabelPrefix + 'storedrequest'] + ' to replace placementId'); return arr[whitelabelPrefix + 'storedrequest']; } else { - this.logError(`GET ${whitelabelPrefix}storedrequest FAILED VALIDATION - will not use it`); + logError(`GET ${whitelabelPrefix}storedrequest FAILED VALIDATION - will not use it`); } } return null; @@ -680,9 +764,9 @@ export const spec = { handleTTDId(eids, validBidRequests) { let ttdId = null; let adsrvrOrgId = config.getConfig('adsrvrOrgId'); - if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.tdid'))) { + if (isStr(deepAccess(validBidRequests, '0.userId.tdid'))) { ttdId = validBidRequests[0].userId.tdid; - } else if (adsrvrOrgId && utils.isStr(adsrvrOrgId.TDID)) { + } else if (adsrvrOrgId && isStr(adsrvrOrgId.TDID)) { ttdId = adsrvrOrgId.TDID; } if (ttdId !== null) { @@ -698,7 +782,6 @@ export const spec = { }); } }, - // Try to use this as the mechanism for reading GET params because it's easy to mock it for tests getGetParametersAsObject() { let items = location.search.substr(1).split('&'); let ret = {}; @@ -714,10 +797,9 @@ export const spec = { * @return {boolean|*[]} true = block the request, else false */ blockTheRequest() { - // if there is an ozone.oz_request = false then quit now. let ozRequest = this.getWhitelabelConfigItem('ozone.oz_request'); if (typeof ozRequest == 'boolean' && !ozRequest) { - this.logWarn(`Will not allow auction : ${this.propertyBag.whitelabel.keyPrefix}one.${this.propertyBag.whitelabel.keyPrefix}_request is set to false`); + logWarn(`Will not allow auction : ${this.propertyBag.whitelabel.keyPrefix}one.${this.propertyBag.whitelabel.keyPrefix}_request is set to false`); return true; } return false; @@ -765,10 +847,9 @@ export const spec = { ret.ext[key] = objConfig[key]; } } - // handle ext separately, if it exists; we have probably built up an ext object already if (objConfig.hasOwnProperty('ext') && typeof objConfig.ext === 'object') { if (objConfig.hasOwnProperty('ext')) { - ret.ext = utils.mergeDeep(ret.ext, objConfig.ext); + ret.ext = mergeDeep(ret.ext, objConfig.ext); } else { ret.ext = objConfig.ext; } @@ -789,14 +870,13 @@ export const spec = { * @private */ _addVideoDefaults(objRet, objConfig, addIfMissing) { - // add inferred values & any default values we want. - let context = utils.deepAccess(objConfig, 'context'); + let context = deepAccess(objConfig, 'context'); if (context === 'outstream') { objRet.placement = 3; } else if (context === 'instream') { objRet.placement = 1; } - let skippable = utils.deepAccess(objConfig, 'skippable', null); + let skippable = deepAccess(objConfig, 'skippable', null); if (skippable == null) { if (addIfMissing && !objRet.hasOwnProperty('skip')) { objRet.skip = skippable ? 1 : 0; @@ -815,13 +895,11 @@ export const spec = { * @returns seatbid object */ export function injectAdIdsIntoAllBidResponses(seatbid) { - spec.logInfo('injectAdIdsIntoAllBidResponses', seatbid); + logInfo('injectAdIdsIntoAllBidResponses', seatbid); for (let i = 0; i < seatbid.length; i++) { let sb = seatbid[i]; for (let j = 0; j < sb.bid.length; j++) { - // modify the bidId per-bid, so each bid has a unique adId within this response, and dfp can select one. - // 2020-06 we now need a second level of ID because there might be multiple identical impid's within a seatbid! - sb.bid[j]['adId'] = `${sb.bid[j]['impid']}-${i}-${j}`; + sb.bid[j]['adId'] = `${sb.bid[j]['impid']}-${i}-${spec.propertyBag.whitelabel.keyPrefix}-${j}`; } } return seatbid; @@ -841,7 +919,7 @@ export function checkDeepArray(Arr) { export function defaultSize(thebidObj) { if (!thebidObj) { - spec.logInfo('defaultSize received empty bid obj! going to return fixed default size'); + logInfo('defaultSize received empty bid obj! going to return fixed default size'); return { 'defaultHeight': 250, 'defaultWidth': 300 @@ -868,7 +946,6 @@ export function ozoneGetWinnerForRequestBid(requestBidId, serverResponseSeatBid) let thisSeat = serverResponseSeatBid[j].seat; for (let k = 0; k < theseBids.length; k++) { if (theseBids[k].impid === requestBidId) { - // we've found a matching server response bid for this request bid if ((thisBidWinner == null) || (thisBidWinner.price < theseBids[k].price)) { thisBidWinner = theseBids[k]; winningSeat = thisSeat; @@ -894,7 +971,6 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { for (let k = 0; k < theseBids.length; k++) { if (theseBids[k].impid === matchBidId) { if (objBids.hasOwnProperty(thisSeat)) { // > 1 bid for an adunit from a bidder - only use the one with the highest bid - // objBids[`${thisSeat}${theseBids[k].w}x${theseBids[k].h}`] = theseBids[k]; if (objBids[thisSeat]['price'] < theseBids[k].price) { objBids[thisSeat] = theseBids[k]; } @@ -919,15 +995,14 @@ export function getRoundedBid(price, mediaType) { let theConfigObject = getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets); let theConfigKey = getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets); - spec.logInfo('getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets); + logInfo('getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets); let priceStringsObj = getPriceBucketString( price, theConfigObject, config.getConfig('currency.granularityMultiplier') ); - spec.logInfo('priceStringsObj', priceStringsObj); - // by default, without any custom granularity set, you get granularity name : 'medium' + logInfo('priceStringsObj', priceStringsObj); let granularityNamePriceStringsKeyMapping = { 'medium': 'med', 'custom': 'custom', @@ -937,7 +1012,7 @@ export function getRoundedBid(price, mediaType) { }; if (granularityNamePriceStringsKeyMapping.hasOwnProperty(theConfigKey)) { let priceStringsKey = granularityNamePriceStringsKeyMapping[theConfigKey]; - spec.logInfo('getRoundedBid: looking for priceStringsKey:', priceStringsKey); + logInfo('getRoundedBid: looking for priceStringsKey:', priceStringsKey); return priceStringsObj[priceStringsKey]; } return priceStringsObj['auto']; @@ -1006,15 +1081,15 @@ export function getWidthAndHeightFromVideoObject(objVideo) { return null; } if (playerSize[0] && typeof playerSize[0] === 'object') { - spec.logInfo('getWidthAndHeightFromVideoObject found nested array inside playerSize.', playerSize[0]); + logInfo('getWidthAndHeightFromVideoObject found nested array inside playerSize.', playerSize[0]); playerSize = playerSize[0]; if (typeof playerSize[0] !== 'number' && typeof playerSize[0] !== 'string') { - spec.logInfo('getWidthAndHeightFromVideoObject found non-number/string type inside the INNER array in playerSize. This is totally wrong - cannot continue.', playerSize[0]); + logInfo('getWidthAndHeightFromVideoObject found non-number/string type inside the INNER array in playerSize. This is totally wrong - cannot continue.', playerSize[0]); return null; } } if (playerSize.length !== 2) { - spec.logInfo('getWidthAndHeightFromVideoObject found playerSize with length of ' + playerSize.length + '. This is totally wrong - cannot continue.'); + logInfo('getWidthAndHeightFromVideoObject found playerSize with length of ' + playerSize.length + '. This is totally wrong - cannot continue.'); return null; } return ({'w': playerSize[0], 'h': playerSize[1]}); @@ -1041,17 +1116,17 @@ export function playerSizeIsNestedArray(objVideo) { * @returns {*} */ function getPlayerSizeFromObject(objVideo) { - spec.logInfo('getPlayerSizeFromObject received object', objVideo); - let playerSize = utils.deepAccess(objVideo, 'playerSize'); + logInfo('getPlayerSizeFromObject received object', objVideo); + let playerSize = deepAccess(objVideo, 'playerSize'); if (!playerSize) { - playerSize = utils.deepAccess(objVideo, 'ext.playerSize'); + playerSize = deepAccess(objVideo, 'ext.playerSize'); } if (!playerSize) { - spec.logError('getPlayerSizeFromObject FAILED: no playerSize in video object or ext', objVideo); + logError('getPlayerSizeFromObject FAILED: no playerSize in video object or ext', objVideo); return null; } if (typeof playerSize !== 'object') { - spec.logError('getPlayerSizeFromObject FAILED: playerSize is not an object/array', objVideo); + logError('getPlayerSizeFromObject FAILED: playerSize is not an object/array', objVideo); return null; } return playerSize; @@ -1062,7 +1137,7 @@ function getPlayerSizeFromObject(objVideo) { */ function newRenderer(adUnitCode, rendererOptions = {}) { let isLoaded = window.ozoneVideo; - spec.logInfo(`newRenderer going to set loaded to ${isLoaded ? 'true' : 'false'}`); + logInfo(`newRenderer going to set loaded to ${isLoaded ? 'true' : 'false'}`); const renderer = Renderer.install({ url: spec.getRendererUrl(), config: rendererOptions, @@ -1072,17 +1147,16 @@ function newRenderer(adUnitCode, rendererOptions = {}) { try { renderer.setRender(outstreamRender); } catch (err) { - spec.logError('Prebid Error when calling setRender on renderer', JSON.parse(JSON.stringify(renderer)), err); + logError('Prebid Error when calling setRender on renderer', JSON.parse(JSON.stringify(renderer)), err); } return renderer; } function outstreamRender(bid) { - spec.logInfo('outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid =', JSON.parse(JSON.stringify(bid))); - // push to render queue because ozoneVideo may not be loaded yet + logInfo('outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid =', JSON.parse(JSON.stringify(bid))); bid.renderer.push(() => { window.ozoneVideo.outstreamRender(bid); }); } registerBidder(spec); -utils.logInfo(`*BidAdapter ${OZONEVERSION} was loaded`); +logInfo(`*BidAdapter ${OZONEVERSION} was loaded`); diff --git a/modules/padsquadBidAdapter.js b/modules/padsquadBidAdapter.js index 24b1d5be3be..72449cf28be 100644 --- a/modules/padsquadBidAdapter.js +++ b/modules/padsquadBidAdapter.js @@ -1,5 +1,5 @@ +import { logInfo, deepAccess } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; import {BANNER} from '../src/mediaTypes.js'; const ENDPOINT_URL = 'https://x.padsquad.com/auction'; @@ -89,12 +89,12 @@ export const spec = { }) }) } else { - utils.logInfo('padsquad.interpretResponse :: no valid responses to interpret'); + logInfo('padsquad.interpretResponse :: no valid responses to interpret'); } return bidResponses; }, getUserSyncs: function (syncOptions, serverResponses) { - utils.logInfo('padsquad.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); + logInfo('padsquad.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); let syncs = []; if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { @@ -102,7 +102,7 @@ export const spec = { } serverResponses.forEach(resp => { - const userSync = utils.deepAccess(resp, 'body.ext.usersync'); + const userSync = deepAccess(resp, 'body.ext.usersync'); if (userSync) { let syncDetails = []; Object.keys(userSync).forEach(key => { diff --git a/modules/papyrusBidAdapter.js b/modules/papyrusBidAdapter.js deleted file mode 100644 index a27c5cf618a..00000000000 --- a/modules/papyrusBidAdapter.js +++ /dev/null @@ -1,77 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const PAPYRUS_ENDPOINT = 'https://prebid.papyrus.global'; -const PAPYRUS_CODE = 'papyrus'; - -export const spec = { - code: PAPYRUS_CODE, - - /** - * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: bid => { - return !!(bid && bid.params && bid.params.address && bid.params.placementId); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests) { - const bidParams = []; - utils._each(validBidRequests, function(bid) { - bidParams.push({ - address: bid.params.address, - placementId: bid.params.placementId, - bidId: bid.bidId, - transactionId: bid.transactionId, - sizes: utils.parseSizesInput(bid.sizes) - }); - }); - - return { - method: 'POST', - url: PAPYRUS_ENDPOINT, - data: bidParams - }; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, request) { - const bidResponses = []; - - if (serverResponse && serverResponse.body && serverResponse.body.bids) { - serverResponse.body.bids.forEach(bid => { - const bidResponse = { - requestId: bid.id, - creativeId: bid.id, - adId: bid.id, - transactionId: bid.transactionId, - cpm: bid.cpm, - width: bid.width, - height: bid.height, - currency: bid.currency, - netRevenue: true, - ttl: 300, - ad: bid.ad - } - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - } -}; - -registerBidder(spec); diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index 826dd9a933a..b1553bcb134 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -5,7 +5,9 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js' +// ci trigger: 1 + +import { timestamp, logError, logWarn, isEmpty, contains, inIframe, deepClone, isPlainObject } from '../src/utils.js'; import find from 'core-js-pure/features/array/find.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; @@ -24,7 +26,7 @@ const EXPIRE_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:00 GMT'; const storage = getStorageManager(PARRABLE_GVLID); function getExpirationDate() { - const oneYearFromNow = new Date(utils.timestamp() + ONE_YEAR_MS); + const oneYearFromNow = new Date(timestamp() + ONE_YEAR_MS); return oneYearFromNow.toGMTString(); } @@ -34,40 +36,55 @@ function deserializeParrableId(parrableIdStr) { values.forEach(function(value) { const pair = value.split(':'); - // unpack a value of 1 as true - parrableId[pair[0]] = +pair[1] === 1 ? true : pair[1]; + if (pair[0] === 'ccpaOptout' || pair[0] === 'ibaOptout') { // unpack a value of 0 or 1 as boolean + parrableId[pair[0]] = Boolean(+pair[1]); + } else if (!isNaN(pair[1])) { // convert to number if is a number + parrableId[pair[0]] = +pair[1] + } else { + parrableId[pair[0]] = pair[1] + } }); return parrableId; } -function serializeParrableId(parrableId) { +function serializeParrableId(parrableIdAndParams) { let components = []; - if (parrableId.eid) { - components.push('eid:' + parrableId.eid); + if (parrableIdAndParams.eid) { + components.push('eid:' + parrableIdAndParams.eid); } - if (parrableId.ibaOptout) { + if (parrableIdAndParams.ibaOptout) { components.push('ibaOptout:1'); } - if (parrableId.ccpaOptout) { + if (parrableIdAndParams.ccpaOptout) { components.push('ccpaOptout:1'); } + if (parrableIdAndParams.tpcSupport !== undefined) { + const tpcSupportComponent = parrableIdAndParams.tpcSupport === true ? 'tpc:1' : 'tpc:0'; + const tpcUntil = `tpcUntil:${parrableIdAndParams.tpcUntil}`; + components.push(tpcSupportComponent); + components.push(tpcUntil); + } + if (parrableIdAndParams.filteredUntil) { + components.push(`filteredUntil:${parrableIdAndParams.filteredUntil}`); + components.push(`filterHits:${parrableIdAndParams.filterHits}`); + } return components.join(','); } function isValidConfig(configParams) { if (!configParams) { - utils.logError('User ID - parrableId submodule requires configParams'); + logError('User ID - parrableId submodule requires configParams'); return false; } if (!configParams.partners && !configParams.partner) { - utils.logError('User ID - parrableId submodule requires partner list'); + logError('User ID - parrableId submodule requires partner list'); return false; } if (configParams.storage) { - utils.logWarn('User ID - parrableId submodule does not require a storage config'); + logWarn('User ID - parrableId submodule does not require a storage config'); } return true; } @@ -84,14 +101,29 @@ function encodeBase64UrlSafe(base64) { function readCookie() { const parrableIdStr = storage.getCookie(PARRABLE_COOKIE_NAME); if (parrableIdStr) { - return deserializeParrableId(decodeURIComponent(parrableIdStr)); + const parsedCookie = deserializeParrableId(decodeURIComponent(parrableIdStr)); + const { tpc, tpcUntil, filteredUntil, filterHits, ...parrableId } = parsedCookie; + let { eid, ibaOptout, ccpaOptout, ...params } = parsedCookie; + + if ((Date.now() / 1000) >= tpcUntil) { + params.tpc = undefined; + } + + if ((Date.now() / 1000) < filteredUntil) { + params.shouldFilter = true; + params.filteredUntil = filteredUntil; + } else { + params.shouldFilter = false; + params.filterHits = filterHits; + } + return { parrableId, params }; } return null; } -function writeCookie(parrableId) { - if (parrableId) { - const parrableIdStr = encodeURIComponent(serializeParrableId(parrableId)); +function writeCookie(parrableIdAndParams) { + if (parrableIdAndParams) { + const parrableIdStr = encodeURIComponent(serializeParrableId(parrableIdAndParams)); storage.setCookie(PARRABLE_COOKIE_NAME, parrableIdStr, getExpirationDate(), 'lax'); } } @@ -145,28 +177,28 @@ function shouldFilterImpression(configParams, parrableId) { } function isAllowed() { - if (utils.isEmpty(config.allowedZones) && - utils.isEmpty(config.allowedOffsets)) { + if (isEmpty(config.allowedZones) && + isEmpty(config.allowedOffsets)) { return true; } if (isZoneListed(config.allowedZones, zone)) { return true; } - if (utils.contains(config.allowedOffsets, offset)) { + if (contains(config.allowedOffsets, offset)) { return true; } return false; } function isBlocked() { - if (utils.isEmpty(config.blockedZones) && - utils.isEmpty(config.blockedOffsets)) { + if (isEmpty(config.blockedZones) && + isEmpty(config.blockedOffsets)) { return false; } if (isZoneListed(config.blockedZones, zone)) { return true; } - if (utils.contains(config.blockedOffsets, offset)) { + if (contains(config.blockedOffsets, offset)) { return true; } return false; @@ -175,10 +207,19 @@ function shouldFilterImpression(configParams, parrableId) { return isBlocked() || !isAllowed(); } +function epochFromTtl(ttl) { + return Math.floor((Date.now() / 1000) + ttl); +} + +function incrementFilterHits(parrableId, params) { + params.filterHits += 1; + writeCookie({ ...parrableId, ...params }) +} + function fetchId(configParams, gdprConsentData) { if (!isValidConfig(configParams)) return; - let parrableId = readCookie(); + let { parrableId, params } = readCookie() || {}; if (!parrableId) { parrableId = readLegacyCookies(); migrateLegacyCookies(parrableId); @@ -188,12 +229,14 @@ function fetchId(configParams, gdprConsentData) { return null; } - const eid = (parrableId) ? parrableId.eid : null; + const eid = parrableId ? parrableId.eid : null; const refererInfo = getRefererInfo(); + const tpcSupport = params ? params.tpc : null; + const shouldFilter = params ? params.shouldFilter : null; const uspString = uspDataHandler.getConsentData(); const gdprApplies = (gdprConsentData && typeof gdprConsentData.gdprApplies === 'boolean' && gdprConsentData.gdprApplies); const gdprConsentString = (gdprConsentData && gdprApplies && gdprConsentData.consentString) || ''; - const partners = configParams.partners || configParams.partner + const partners = configParams.partners || configParams.partner; const trackers = typeof partners === 'string' ? partners.split(',') : partners; @@ -203,9 +246,14 @@ function fetchId(configParams, gdprConsentData) { trackers, url: refererInfo.referer, prebidVersion: '$prebid.version$', - isIframe: utils.inIframe() + isIframe: inIframe(), + tpcSupport }; + if (shouldFilter === false) { + data.filterHits = params.filterHits; + } + const searchParams = { data: encodeBase64UrlSafe(btoa(JSON.stringify(data))), gdpr: gdprApplies ? 1 : 0, @@ -228,7 +276,8 @@ function fetchId(configParams, gdprConsentData) { const callback = function (cb) { const callbacks = { success: response => { - let newParrableId = parrableId ? utils.deepClone(parrableId) : {}; + let newParrableId = parrableId ? deepClone(parrableId) : {}; + let newParams = {}; if (response) { try { let responseObj = JSON.parse(response); @@ -242,24 +291,37 @@ function fetchId(configParams, gdprConsentData) { if (responseObj.ibaOptout === true) { newParrableId.ibaOptout = true; } + if (responseObj.tpcSupport !== undefined) { + newParams.tpcSupport = responseObj.tpcSupport; + newParams.tpcUntil = epochFromTtl(responseObj.tpcSupportTtl); + } + if (responseObj.filterTtl) { + newParams.filteredUntil = epochFromTtl(responseObj.filterTtl); + newParams.filterHits = 0; + } } } catch (error) { - utils.logError(error); + logError(error); cb(); } - writeCookie(newParrableId); + writeCookie({ ...newParrableId, ...newParams }); cb(newParrableId); } else { - utils.logError('parrableId: ID fetch returned an empty result'); + logError('parrableId: ID fetch returned an empty result'); cb(); } }, error: error => { - utils.logError(`parrableId: ID fetch encountered an error`, error); + logError(`parrableId: ID fetch encountered an error`, error); cb(); } }; - ajax(PARRABLE_URL, callbacks, searchParams, options); + + if (shouldFilter) { + incrementFilterHits(parrableId, params); + } else { + ajax(PARRABLE_URL, callbacks, searchParams, options); + } }; return { @@ -288,7 +350,7 @@ export const parrableIdSubmodule = { * @return {(Object|undefined} */ decode(parrableId) { - if (parrableId && utils.isPlainObject(parrableId)) { + if (parrableId && isPlainObject(parrableId)) { return { parrableId }; } return undefined; diff --git a/modules/performaxBidAdapter.js b/modules/performaxBidAdapter.js deleted file mode 100644 index 8e22a0b2da9..00000000000 --- a/modules/performaxBidAdapter.js +++ /dev/null @@ -1,56 +0,0 @@ -import {logWarn} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const CLIENT = 'hellboy:v0.0.1' -const BIDDER_CODE = 'performax'; -const BIDDER_SHORT_CODE = 'px'; -const ENDPOINT = 'https://dale.performax.cz/hb'; - -export const spec = { - code: BIDDER_CODE, - aliases: [BIDDER_SHORT_CODE], - - isBidRequestValid: function (bid) { - return !!bid.params.slotId; - }, - - buildUrl: function (validBidRequests, bidderRequest) { - const slotIds = validBidRequests.map(request => request.params.slotId); - let url = [`${ENDPOINT}?slotId[]=${slotIds.join()}`]; - url.push('client=' + CLIENT); - url.push('auctionId=' + bidderRequest.auctionId); - return url.join('&'); - }, - - buildRequests: function (validBidRequests, bidderRequest) { - return { - method: 'POST', - url: this.buildUrl(validBidRequests, bidderRequest), - data: {'validBidRequests': validBidRequests, 'bidderRequest': bidderRequest}, - options: {contentType: 'application/json'}, - } - }, - - buildHtml: function (ad) { - const keys = Object.keys(ad.data || {}); - return ad.code.replace( - new RegExp('\\$(' + keys.join('|') + ')\\$', 'g'), - (matched, key) => ad.data[key] || matched - ); - }, - - interpretResponse: function (serverResponse, request) { - let bidResponses = []; - for (let i = 0; i < serverResponse.body.length; i++) { - const ad = serverResponse.body[i].ad; - if (ad.type === 'empty') { - logWarn(`One of ads is empty (reason=${ad.reason})`); - continue; - } - serverResponse.body[i].ad = this.buildHtml(ad); - bidResponses.push(serverResponse.body[i]); - } - return bidResponses; - } -} -registerBidder(spec); diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 91e88d3e4e1..40282567506 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -9,35 +9,33 @@ import { getGlobal } from '../src/prebidGlobal.js' import { submodule } from '../src/hook.js' import { getStorageManager } from '../src/storageManager.js' import { deepSetValue, deepAccess, isFn, mergeDeep, logError } from '../src/utils.js' +import { config } from '../src/config.js' import includes from 'core-js-pure/features/array/includes.js' const MODULE_NAME = 'permutive' export const storage = getStorageManager(null, MODULE_NAME) -function init (config, userConsent) { +function init (moduleConfig, userConsent) { return true } /** -* Set segment targeting from cache and then try to wait for Permutive -* to initialise to get realtime segment targeting -*/ -export function initSegments (reqBidsConfigObj, callback, customConfig) { + * Set segment targeting from cache and then try to wait for Permutive + * to initialise to get realtime segment targeting + * @param {Object} reqBidsConfigObj + * @param {function} callback - Called when submodule is done + * @param {customModuleConfig} reqBidsConfigObj - Publisher config for module + */ +export function initSegments (reqBidsConfigObj, callback, customModuleConfig) { const permutiveOnPage = isPermutiveOnPage() - const config = mergeDeep({ - waitForIt: false, - params: { - maxSegs: 500, - acBidders: [], - overwrites: {} - } - }, customConfig) + const moduleConfig = getModuleConfig(customModuleConfig) + const segmentData = getSegments(moduleConfig.params.maxSegs) - setSegments(reqBidsConfigObj, config) + setSegments(reqBidsConfigObj, moduleConfig, segmentData) - if (config.waitForIt && permutiveOnPage) { + if (moduleConfig.waitForIt && permutiveOnPage) { window.permutive.ready(function () { - setSegments(reqBidsConfigObj, config) + setSegments(reqBidsConfigObj, moduleConfig, segmentData) callback() }, 'realtime') } else { @@ -45,27 +43,107 @@ export function initSegments (reqBidsConfigObj, callback, customConfig) { } } -function setSegments (reqBidsConfigObj, config) { - const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits - const data = getSegments(config.params.maxSegs) +/** + * Merges segments into existing bidder config + * @param {Object} customModuleConfig - Publisher config for module + * @return {Object} Merged defatul and custom config + */ +function getModuleConfig (customModuleConfig) { + return mergeDeep({ + waitForIt: false, + params: { + maxSegs: 500, + acBidders: [], + overwrites: {} + } + }, customModuleConfig) +} + +/** + * Sets ortb2 config for ac bidders + * @param {Object} auctionDetails + * @param {Object} customModuleConfig - Publisher config for module + */ +export function setBidderRtb (auctionDetails, customModuleConfig) { + const bidderConfig = config.getBidderConfig() + const moduleConfig = getModuleConfig(customModuleConfig) + const acBidders = deepAccess(moduleConfig, 'params.acBidders') + const maxSegs = deepAccess(moduleConfig, 'params.maxSegs') + const segmentData = getSegments(maxSegs) + + acBidders.forEach(function (bidder) { + const currConfig = bidderConfig[bidder] || {} + const nextConfig = mergeOrtbConfig(currConfig, segmentData) + + config.setBidderConfig({ + bidders: [bidder], + config: nextConfig + }) + }) +} + +/** + * Merges segments into existing bidder config + * @param {Object} currConfig - Current bidder config + * @param {Object} segmentData - Segment data + * @return {Object} Merged ortb2 object + */ +function mergeOrtbConfig (currConfig, segmentData) { + const segment = segmentData.ac.map(seg => { + return { id: seg } + }) + const name = 'permutive.com' + const ortbConfig = mergeDeep({}, currConfig) + const currSegments = deepAccess(ortbConfig, 'ortb2.user.data') || [] + const userSegment = currSegments + .filter(el => el.name !== name) + .concat({ name, segment }) + + deepSetValue(ortbConfig, 'ortb2.user.data', userSegment) + + return ortbConfig +} + +/** + * Set segments on bid request object + * @param {Object} reqBidsConfigObj - Bid request object + * @param {Object} moduleConfig - Module configuration + * @param {Object} segmentData - Segment object + */ +function setSegments (reqBidsConfigObj, moduleConfig, segmentData) { + const adUnits = (reqBidsConfigObj && reqBidsConfigObj.adUnits) || getGlobal().adUnits const utils = { deepSetValue, deepAccess, isFn, mergeDeep } + const aliasMap = { + appnexusAst: 'appnexus' + } + + if (!adUnits) { + return + } adUnits.forEach(adUnit => { adUnit.bids.forEach(bid => { - const { bidder } = bid - const acEnabled = isAcEnabled(config, bidder) - const customFn = getCustomBidderFn(config, bidder) + let { bidder } = bid + if (typeof aliasMap[bidder] !== 'undefined') { + bidder = aliasMap[bidder] + } + const acEnabled = isAcEnabled(moduleConfig, bidder) + const customFn = getCustomBidderFn(moduleConfig, bidder) const defaultFn = getDefaultBidderFn(bidder) if (customFn) { - customFn(bid, data, acEnabled, utils, defaultFn) + customFn(bid, segmentData, acEnabled, utils, defaultFn) } else if (defaultFn) { - defaultFn(bid, data, acEnabled) + defaultFn(bid, segmentData, acEnabled) } }) }) } +/** + * Catch and log errors + * @param {function} fn - Function to safely evaluate + */ function makeSafe (fn) { try { fn() @@ -74,8 +152,8 @@ function makeSafe (fn) { } } -function getCustomBidderFn (config, bidder) { - const overwriteFn = deepAccess(config, `params.overwrites.${bidder}`) +function getCustomBidderFn (moduleConfig, bidder) { + const overwriteFn = deepAccess(moduleConfig, `params.overwrites.${bidder}`) if (overwriteFn && isFn(overwriteFn)) { return overwriteFn @@ -85,13 +163,13 @@ function getCustomBidderFn (config, bidder) { } /** -* Returns a function that receives a `bid` object, a `data` object and a `acEnabled` boolean -* and which will set the right segment targeting keys for `bid` based on `data` and `acEnabled` -* @param {string} bidder -* @param {object} data -*/ + * Returns a function that receives a `bid` object, a `data` object and a `acEnabled` boolean + * and which will set the right segment targeting keys for `bid` based on `data` and `acEnabled` + * @param {string} bidder - Bidder name + * @return {Object} Bidder function + */ function getDefaultBidderFn (bidder) { - const bidderMapper = { + const bidderMap = { appnexus: function (bid, data, acEnabled) { if (acEnabled && data.ac && data.ac.length) { deepSetValue(bid, 'params.keywords.p_standard', data.ac) @@ -117,32 +195,37 @@ function getDefaultBidderFn (bidder) { deepSetValue(bid, 'params.customData.0.targeting.p_standard', data.ac) } - return bid - }, - trustx: function (bid, data, acEnabled) { - if (acEnabled && data.ac && data.ac.length) { - deepSetValue(bid, 'params.keywords.p_standard', data.ac) - } - return bid } } - return bidderMapper[bidder] + return bidderMap[bidder] } -export function isAcEnabled (config, bidder) { - const acBidders = deepAccess(config, 'params.acBidders') || [] +/** + * Check whether ac is enabled for bidder + * @param {Object} moduleConfig - Module configuration + * @param {string} bidder - Bidder name + * @return {boolean} + */ +export function isAcEnabled (moduleConfig, bidder) { + const acBidders = deepAccess(moduleConfig, 'params.acBidders') || [] return includes(acBidders, bidder) } +/** + * Check whether Permutive is on page + * @return {boolean} + */ export function isPermutiveOnPage () { return typeof window.permutive !== 'undefined' && typeof window.permutive.ready === 'function' } /** -* Returns all relevant segment IDs in an object -*/ + * Get all relevant segment IDs in an object + * @param {number} maxSegs - Maximum number of segments to be included + * @return {Object} + */ export function getSegments (maxSegs) { const legacySegs = readSegments('_psegs').map(Number).filter(seg => seg >= 1000000).map(String) const _ppam = readSegments('_ppam') @@ -166,6 +249,7 @@ export function getSegments (maxSegs) { * Gets an array of segment IDs from LocalStorage * or returns an empty array * @param {string} key + * @return {string[]|number[]} */ function readSegments (key) { try { @@ -178,9 +262,16 @@ function readSegments (key) { /** @type {RtdSubmodule} */ export const permutiveSubmodule = { name: MODULE_NAME, - getBidRequestData: function (reqBidsConfigObj, callback, customConfig) { + getBidRequestData: function (reqBidsConfigObj, callback, customModuleConfig) { + makeSafe(function () { + // Legacy route with custom parameters + initSegments(reqBidsConfigObj, callback, customModuleConfig) + }) + }, + onAuctionInitEvent: function (auctionDetails, customModuleConfig) { makeSafe(function () { - initSegments(reqBidsConfigObj, callback, customConfig) + // Route for bidders supporting ORTB2 + setBidderRtb(auctionDetails, customModuleConfig) }) }, init: init diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index fe8c34c1b5c..0acd42405d1 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -1,11 +1,14 @@ # Permutive Real-time Data Submodule -This submodule reads segments from Permutive and attaches them as targeting keys to bid requests. Using this module will deliver best targeting results, leveraging Permutive's real-time segmentation and modelling capabilities. +This submodule reads cohorts from Permutive and attaches them as targeting keys to bid requests. Using this module will deliver best targeting results, leveraging Permutive's real-time segmentation and modelling capabilities. ## Usage Compile the Permutive RTD module into your Prebid build: ``` -gulp build --modules=permutiveRtdProvider +gulp build --modules=rtdModule,permutiveRtdProvider ``` + +> Note that the global RTD module, `rtdModule`, is a prerequisite of the Permutive RTD module. + You then need to enable the Permutive RTD in your Prebid configuration, using the below format: ```javascript @@ -17,7 +20,7 @@ pbjs.setConfig({ name: 'permutive', waitForIt: true, // should be true if there's an `auctionDelay` params: { - acBidders: ['appnexus', 'rubicon', 'ozone'] + acBidders: ['appnexus'] } }] }, @@ -26,18 +29,18 @@ pbjs.setConfig({ ``` ## Supported Bidders -The below bidders are currently support by the Permutive RTD module. Please reach out to your Permutive Account Manager to request support for any additional bidders. +The Permutive RTD module sets Audience Connector cohorts as bidder-specific `ortb2.user.data` first-party data, following the Prebid `ortb2` convention, for any bidder included in `acBidders`. The module also supports bidder-specific data locations per ad unit (custom parameters) for the below bidders: -| Bidder | ID | First-party segments | Audience Connector | +| Bidder | ID | Custom Cohorts | Audience Connector | | ----------- | ---------- | -------------------- | ------------------ | | Xandr | `appnexus` | Yes | Yes | -| Magnite | `rubicon` | Yes | Yes | +| Magnite | `rubicon` | Yes | No | | Ozone | `ozone` | No | Yes | -| TrustX | `trustx` | No | Yes | -* **First-party segments:** When enabling the respective Activation for a segment in Permutive, this module will automatically attach that segment to the bid request. There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in Permutive. Permutive segments will be sent in the `permutive` key-value. +Key-values details for custom parameters: +* **Custom Cohorts:** When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. Permutive cohorts will be sent in the `permutive` key-value. -* **Audience Connector:** You'll need to define which bidder should receive Audience Connector segments. You need to include the `ID` of any bidder in the `acBidders` array. Audience Connector segments will be sent in the `p_standard` key-value. +* **Audience Connector:** You'll need to define which bidders should receive Audience Connector cohorts. You need to include the `ID` of any bidder in the `acBidders` array. Audience Connector cohorts will be sent in the `p_standard` key-value. ## Parameters @@ -46,55 +49,5 @@ The below bidders are currently support by the Permutive RTD module. Please reac | name | String | This should always be `permutive` | - | | waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | | params | Object | | - | -| params.acBidders | String[] | An array of bidders which should receive AC segments. Pleasee see `Supported Bidders` for bidder support and possible values. | `[]` | -| params.maxSegs | Integer | Maximum number of segments to be included in either the `permutive` or `p_standard` key-value. | `500` | -| params.overwrites | Object | See `Custom Bidder Setup` for details on how to define custom bidder functions. | `{}` | - - -## Custom Bidder Setup -You can overwrite the default bidder function, for example to include a different set of segments or to support additional bidders. The below example modifies what first-party segments Magnite receives (segments from `gam` instead of `rubicon`). As best practise we recommend to first call `defaultFn` and then only overwrite specific key-values. The below example only overwrites `permutive` while `p_standard` are still set by `defaultFn` (if `rubicon` is an enabled `acBidder`). - -```javascript -pbjs.setConfig({ - ..., - realTimeData: { - auctionDelay: 50, - dataProviders: [{ - name: 'permutive', - waitForIt: true, - params: { - acBidders: ['appnexus', 'rubicon'], - maxSegs: 450, - overwrites: { - rubicon: function (bid, data, acEnabled, utils, defaultFn) { - if (defaultFn){ - bid = defaultFn(bid, data, acEnabled) - } - if (data.gam && data.gam.length) { - utils.deepSetValue(bid, 'params.visitor.permutive', data.gam) - } - } - } - } - }] - }, - ... -}) -``` -Any custom bidder function will receive the following parameters: - -| Name | Type | Description | -| ------------- |-------------- | --------------------------------------- | -| bid | Object | The bidder specific bidder object. You will mutate this object to set the appropriate targeting keys. | -| data | Object | An object containing Permutive segments | -| data.appnexus | string[] | Segments exposed by the Xandr SSP integration | -| data.rubicon | string[] | Segments exposed by the Magnite SSP integration | -| data.gam | string[] | Segments exposed by the Google Ad Manager integration | -| data.ac | string[] | Segments exposed by the Audience Connector | -| acEnabled | Boolean | `true` if the current bidder in included in `params.acBidders` | -| utils | {} | An object containing references to various util functions used by `permutiveRtdProvider.js`. Please make sure not to overwrite any of these. | -| defaultFn | Function | The default function for this bidder. Please note that this can be `undefined` if there is no default function for this bidder (see `Supported Bidders`). The function expect the following parameters: `bid`, `data`, `acEnabled` and will return `bid`. | - -**Warning** - -The custom bidder function will mutate the `bid` object. Please be aware that this could break your bid request if you accidentally overwrite any fields other than the `permutive` or `p_standard` key-values or if you change the structure of the `bid` object in any way. +| params.acBidders | String[] | An array of bidders which should receive AC cohorts. | `[]` | +| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | diff --git a/modules/pilotxBidAdapter.js b/modules/pilotxBidAdapter.js new file mode 100644 index 00000000000..335c461e3d9 --- /dev/null +++ b/modules/pilotxBidAdapter.js @@ -0,0 +1,147 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +const BIDDER_CODE = 'pilotx'; +const ENDPOINT_URL = '//adn.pilotx.tv/hb' +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + aliases: ['pilotx'], // short code + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + let sizesCheck = !!bid.sizes + let paramSizesCheck = !!bid.params.sizes + var sizeConfirmed = false + if (sizesCheck) { + if (bid.sizes.length < 1) { + return false + } else { + sizeConfirmed = true + } + } + if (paramSizesCheck) { + if (bid.params.sizes.length < 1 && !sizeConfirmed) { + return false + } else { + sizeConfirmed = true + } + } + if (!sizeConfirmed) { + return false + } + return !!(bid.params.placementId); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + let payloadItems = {}; + validBidRequests.forEach(bidRequest => { + let sizes = []; + let placementId = this.setPlacementID(bidRequest.params.placementId) + payloadItems[placementId] = {} + if (bidRequest.sizes.length > 0) { + if (Array.isArray(bidRequest.sizes[0])) { + for (let i = 0; i < bidRequest.sizes.length; i++) { + sizes[i] = [(bidRequest.sizes[i])[0], (bidRequest.sizes[i])[1]] + } + } else { + sizes[0] = [bidRequest.sizes[0], bidRequest.sizes[1]] + } + payloadItems[placementId]['sizes'] = sizes + } + if (bidRequest.mediaTypes != null) { + for (let i in bidRequest.mediaTypes) { + payloadItems[placementId][i] = { + ...bidRequest.mediaTypes[i] + } + } + } + let consentTemp = '' + let consentRequiredTemp = false + if (bidderRequest && bidderRequest.gdprConsent) { + consentTemp = bidderRequest.gdprConsent.consentString + // will check if the gdprApplies field was populated with a boolean value (ie from page config). If it's undefined, then default to true + consentRequiredTemp = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + } + + payloadItems[placementId]['gdprConsentString'] = consentTemp + payloadItems[placementId]['gdprConsentRequired'] = consentRequiredTemp + payloadItems[placementId]['bidId'] = bidRequest.bidId + }); + const payload = payloadItems; + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadString, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const serverBody = serverResponse.body; + const bidResponses = []; + if (serverBody.mediaType == 'banner') { + const bidResponse = { + requestId: serverBody.requestId, + cpm: serverBody.cpm, + width: serverBody.width, + height: serverBody.height, + creativeId: serverBody.creativeId, + currency: serverBody.currency, + netRevenue: false, + ttl: serverBody.ttl, + ad: serverBody.ad, + mediaType: 'banner', + meta: { + mediaType: 'banner', + advertiserDomains: serverBody.advertiserDomains + } + } + bidResponses.push(bidResponse) + } else if (serverBody.mediaType == 'video') { + const bidResponse = { + requestId: serverBody.requestId, + cpm: serverBody.cpm, + width: serverBody.width, + height: serverBody.height, + creativeId: serverBody.creativeId, + currency: serverBody.currency, + netRevenue: false, + ttl: serverBody.ttl, + vastUrl: serverBody.vastUrl, + mediaType: 'video', + meta: { + mediaType: 'video', + advertiserDomains: serverBody.advertiserDomains + } + } + bidResponses.push(bidResponse) + } + + return bidResponses; + }, + + /** + * Formats placement ids for adserver ingestion purposes + * @param {string[]} The placement ID/s in an array + */ + setPlacementID: function (placementId) { + if (Array.isArray(placementId)) { + return placementId.join('#') + } + return placementId + }, +} +registerBidder(spec); diff --git a/modules/pilotxBidAdapter.md b/modules/pilotxBidAdapter.md new file mode 100644 index 00000000000..37489bda4a0 --- /dev/null +++ b/modules/pilotxBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +``` +Module Name: Pilotx Prebid Adapter +Module Type: Bidder Adapter +Maintainer: tony@pilotx.tv +``` + +# Description + +Connects to Pilotx + +Pilotx's bid adapter supports banner and video. + +# Test Parameters +``` +// Banner adUnit +var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + bids: [{ + bidder: 'pilotx', + params: { + placementId: ["1423"] + } + }] + +}]; + +// Video adUnit +var videoAdUnit = { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + } + }, + bids: [{ + bidder: 'pilotx', + params: { + placementId: '1422', + } + }] +}; +``` \ No newline at end of file diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js new file mode 100644 index 00000000000..e9db875fc2f --- /dev/null +++ b/modules/pixfutureBidAdapter.js @@ -0,0 +1,343 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { convertCamelToUnderscore, isArray, isNumber, isPlainObject, deepAccess, isEmpty, transformBidderParamKeywords, isFn } from '../src/utils.js'; +import { auctionManager } from '../src/auctionManager.js'; +import find from 'core-js-pure/features/array/find.js'; + +const SOURCE = 'pbjs'; +const storageManager = getStorageManager(); +const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; +export const spec = { + code: 'pixfuture', + hostname: 'https://gosrv.pixfuture.com', + + getHostname() { + let ret = this.hostname; + try { + ret = storageManager.getDataFromLocalStorage('ov_pixbidder_host') || ret; + } catch (e) { + } + return ret; + }, + + isBidRequestValid(bid) { + return !!(bid.sizes && bid.bidId && bid.params && + (bid.params.pix_id && (typeof bid.params.pix_id === 'string'))); + }, + + buildRequests(validBidRequests, bidderRequest) { + const tags = validBidRequests.map(bidToTag); + const hostname = this.getHostname(); + return validBidRequests.map((bidRequest) => { + let referer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referer = bidderRequest.refererInfo.referer || ''; + } + + const userObjBid = find(validBidRequests, hasUserInfo); + let userObj = {}; + if (config.getConfig('coppa') === true) { + userObj = {'coppa': true}; + } + + if (userObjBid) { + Object.keys(userObjBid.params.user) + .filter(param => includes(USER_PARAMS, param)) + .forEach((param) => { + let uparam = convertCamelToUnderscore(param); + if (param === 'segments' && isArray(userObjBid.params.user[param])) { + let segs = []; + userObjBid.params.user[param].forEach(val => { + if (isNumber(val)) { + segs.push({'id': val}); + } else if (isPlainObject(val)) { + segs.push(val); + } + }); + userObj[uparam] = segs; + } else if (param !== 'segments') { + userObj[uparam] = userObjBid.params.user[param]; + } + }); + } + + const schain = validBidRequests[0].schain; + + const payload = { + tags: [...tags], + user: userObj, + sdk: { + source: SOURCE, + version: '$prebid.version$' + }, + schain: schain + }; + + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + + if (bidderRequest && bidderRequest.refererInfo) { + let refererinfo = { + rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + }; + payload.referrer_detection = refererinfo; + } + + if (validBidRequests[0].userId) { + let eids = []; + + addUserId(eids, deepAccess(validBidRequests[0], `userId.flocId.id`), 'chrome.com', null); + addUserId(eids, deepAccess(validBidRequests[0], `userId.criteoId`), 'criteo.com', null); + addUserId(eids, deepAccess(validBidRequests[0], `userId.unifiedId`), 'thetradedesk.com', null); + addUserId(eids, deepAccess(validBidRequests[0], `userId.id5Id`), 'id5.io', null); + addUserId(eids, deepAccess(validBidRequests[0], `userId.sharedId`), 'thetradedesk.com', null); + addUserId(eids, deepAccess(validBidRequests[0], `userId.identityLink`), 'liveramp.com', null); + addUserId(eids, deepAccess(validBidRequests[0], `userId.liveIntentId`), 'liveintent.com', null); + addUserId(eids, deepAccess(validBidRequests[0], `userId.fabrickId`), 'home.neustar', null); + + if (eids.length) { + payload.eids = eids; + } + } + + if (tags[0].publisher_id) { + payload.publisher_id = tags[0].publisher_id; + } + + const ret = { + url: `${hostname}/pixservices`, + method: 'POST', + options: {withCredentials: false}, + data: { + v: $$PREBID_GLOBAL$$.version, + pageUrl: referer, + bidId: bidRequest.bidId, + auctionId: bidRequest.auctionId, + transactionId: bidRequest.transactionId, + adUnitCode: bidRequest.adUnitCode, + bidRequestCount: bidRequest.bidRequestCount, + sizes: bidRequest.sizes, + params: bidRequest.params, + pubext: payload + } + }; + if (bidderRequest && bidderRequest.gdprConsent) { + ret.data.gdprConsent = { + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') && bidderRequest.gdprConsent.gdprApplies + }; + } + return ret; + }); + }, + + interpretResponse: function (serverResponse, { bidderRequest }) { + serverResponse = serverResponse.body; + const bids = []; + if (serverResponse.creatives.bids && serverResponse.placements) { + serverResponse.placements.forEach(serverBid => { + serverBid.creatives.forEach(creative => { + const bid = newBid(serverBid, creative, serverBid.placement_id, serverBid.uuid); + bid.mediaType = BANNER; + bids.push(bid); + }); + }); + } + + return bids; + }, + getUserSyncs: function (syncOptions, bid, gdprConsent) { + var pixid = ''; + if (typeof bid[0] === 'undefined' || bid[0] === null) { pixid = '0'; } else { pixid = bid[0].body.pix_id; } + if (syncOptions.iframeEnabled && hasPurpose1Consent({gdprConsent})) { + return [{ + type: 'iframe', + url: 'https://gosrv.pixfuture.com/cookiesync?adsync=' + gdprConsent.consentString + '&pixid=' + pixid + '&gdprconcent=' + gdprConsent.gdprApplies + }]; + } + } +}; + +function newBid(serverBid, rtbBid, placementId, uuid) { + const bid = { + requestId: uuid, + cpm: rtbBid.cpm, + creativeId: rtbBid.creative_id, + currency: 'USD', + netRevenue: true, + ttl: 300, + adUnitCode: placementId + }; + + if (rtbBid.adomain) { + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [rtbBid.adomain] }); + }; + + Object.assign(bid, { + width: rtbBid.width, + height: rtbBid.height, + ad: rtbBid.code + }); + + return bid; +} + +function hasPurpose1Consent(bidderRequest) { + let result = true; + if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { + result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); + } + } + return result; +} + +// Functions related optional parameters +function bidToTag(bid) { + const tag = {}; + tag.sizes = transformSizes(bid.sizes); + tag.primary_size = tag.sizes[0]; + tag.ad_types = []; + tag.uuid = bid.bidId; + if (bid.params.placementId) { + tag.id = parseInt(bid.params.placementId, 10); + } else { + tag.code = bid.params.invCode; + } + tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; + tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.prebid = true; + tag.disable_psa = true; + let bidFloor = getBidFloor(bid); + if (bidFloor) { + tag.reserve = bidFloor; + } + if (bid.params.position) { + tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; + } + if (bid.params.trafficSourceCode) { + tag.traffic_source_code = bid.params.trafficSourceCode; + } + if (bid.params.privateSizes) { + tag.private_sizes = transformSizes(bid.params.privateSizes); + } + if (bid.params.supplyType) { + tag.supply_type = bid.params.supplyType; + } + if (bid.params.pubClick) { + tag.pubclick = bid.params.pubClick; + } + if (bid.params.extInvCode) { + tag.ext_inv_code = bid.params.extInvCode; + } + if (bid.params.publisherId) { + tag.publisher_id = parseInt(bid.params.publisherId, 10); + } + if (bid.params.externalImpId) { + tag.external_imp_id = bid.params.externalImpId; + } + if (!isEmpty(bid.params.keywords)) { + let keywords = transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + tag.keywords = keywords; + } + + let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + tag.gpid = gpid; + } + + if (bid.renderer) { + tag.video = Object.assign({}, tag.video, {custom_renderer_present: true}); + } + + if (bid.params.frameworks && isArray(bid.params.frameworks)) { + tag['banner_frameworks'] = bid.params.frameworks; + } + + let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); + if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { + tag.ad_types.push(BANNER); + } + + if (tag.ad_types.length === 0) { + delete tag.ad_types; + } + + return tag; +} + +function addUserId(eids, id, source, rti) { + if (id) { + if (rti) { + eids.push({source, id, rti_partner: rti}); + } else { + eids.push({source, id}); + } + } + return eids; +} + +function hasUserInfo(bid) { + return !!bid.params.user; +} + +function transformSizes(requestSizes) { + let sizes = []; + let sizeObj = {}; + + if (isArray(requestSizes) && requestSizes.length === 2 && + !isArray(requestSizes[0])) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + sizes.push(sizeObj); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + sizes.push(sizeObj); + } + } + + return sizes; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.reserve) ? bid.params.reserve : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + +function isPopulatedArray(arr) { + return !!(isArray(arr) && arr.length > 0); +} + +registerBidder(spec); diff --git a/modules/pixfutureBidAdapter.md b/modules/pixfutureBidAdapter.md new file mode 100644 index 00000000000..b7911d6c9bb --- /dev/null +++ b/modules/pixfutureBidAdapter.md @@ -0,0 +1,154 @@ +# Overview + +``` +Module Name: PixFuture Bid Adapter +Module Type: Bidder Adapter +Maintainer: admin@pixfuture.net +``` +# Description + +Module that connects to PixFuture demand sources + +# Test Parameters +``` +var adUnits = [{ + "bidderCode": "pixfuture", + "auctionId": "634c9d0e-306f-4a5c-974e-21697dfd4fcd", + "bidderRequestId": "5f85993da0f6be", + "bids": [ + { + "labelAny": [ + "display" + ], + "bidder": "pixfuture", + "params": { + "pix_id": "Abc123" + }]; +``` + +# Test Example +... + + + + + + + + + + + +

Basic Prebid.js Example

+
Div-1
+
+ +
+ +
+ +
Div-2
+
+ +
+ + + + diff --git a/modules/piximediaBidAdapter.js b/modules/piximediaBidAdapter.js deleted file mode 100644 index 2617cc8fe42..00000000000 --- a/modules/piximediaBidAdapter.js +++ /dev/null @@ -1,47 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'piximedia'; -const ENDPOINT = 'https://ad.piximedia.com/prebid'; - -export const spec = { - code: BIDDER_CODE, - isBidRequestValid: function(bid) { - return !!(bid.params && bid.params.siteId && bid.params.placementId); - }, - buildRequests: function(validBidRequests) { - return validBidRequests.map(bidRequest => { - let parseSized = utils.parseSizesInput(bidRequest.sizes); - let arrSize = parseSized[0].split('x'); - return { - method: 'GET', - url: ENDPOINT, - data: { - timestamp: utils.timestamp(), - pver: '1.0', - pbparams: JSON.stringify(bidRequest.params), - pbsizes: JSON.stringify(parseSized), - pbwidth: arrSize[0], - pbheight: arrSize[1], - pbbidid: bidRequest.bidId, - }, - }; - }); - }, - interpretResponse: function(serverResponse, request) { - const res = serverResponse.body; - const bidResponse = { - requestId: res.bidId, - cpm: parseFloat(res.cpm), - width: res.width, - height: res.height, - creativeId: res.creative_id, - currency: res.currency, - netRevenue: true, - ttl: 300, - ad: res.adm - }; - return [bidResponse]; - } -} -registerBidder(spec); diff --git a/modules/platformioBidAdapter.js b/modules/platformioBidAdapter.js deleted file mode 100644 index 314f738ef81..00000000000 --- a/modules/platformioBidAdapter.js +++ /dev/null @@ -1,307 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import includes from 'core-js-pure/features/array/includes.js'; - -const NATIVE_DEFAULTS = { - TITLE_LEN: 100, - DESCR_LEN: 200, - SPONSORED_BY_LEN: 50, - IMG_MIN: 150, - ICON_MIN: 50, -}; -const DEFAULT_MIMES = ['video/mp4', 'video/webm', 'application/x-shockwave-flash', 'application/javascript']; -const VIDEO_TARGETING = ['mimes', 'skippable', 'playback_method', 'protocols', 'api']; -const DEFAULT_PROTOCOLS = [2, 3, 5, 6]; -const DEFAULT_APIS = [1, 2]; - -export const spec = { - - code: 'platformio', - supportedMediaTypes: ['banner', 'native', 'video'], - - isBidRequestValid: bid => ( - !!(bid && bid.params && bid.params.pubId && bid.params.placementId) - ), - buildRequests: (bidRequests, bidderRequest) => { - const request = { - id: bidRequests[0].bidderRequestId, - at: 2, - imp: bidRequests.map(slot => impression(slot)), - site: site(bidRequests), - app: app(bidRequests), - device: device(bidRequests), - }; - applyGdpr(bidderRequest, request); - return { - method: 'POST', - url: 'https://piohbdisp.hb.adx1.com/', - data: JSON.stringify(request), - }; - }, - interpretResponse: (response, request) => ( - bidResponseAvailable(request, response.body) - ), -}; - -function bidResponseAvailable(bidRequest, bidResponse) { - const idToImpMap = {}; - const idToBidMap = {}; - const ortbRequest = parse(bidRequest.data); - ortbRequest.imp.forEach(imp => { - idToImpMap[imp.id] = imp; - }); - if (bidResponse) { - bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => { - idToBidMap[bid.impid] = bid; - })); - } - const bids = []; - Object.keys(idToImpMap).forEach(id => { - if (idToBidMap[id]) { - const bid = {}; - bid.requestId = id; - bid.adId = id; - bid.creativeId = id; - bid.cpm = idToBidMap[id].price; - bid.currency = bidResponse.cur; - bid.ttl = 360; - bid.netRevenue = true; - if (idToImpMap[id]['native']) { - bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); - let nurl = idToBidMap[id].nurl; - nurl = nurl.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); - nurl = nurl.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); - nurl = nurl.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); - nurl = nurl.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); - bid['native']['impressionTrackers'] = [nurl]; - bid.mediaType = 'native'; - } else if (idToImpMap[id]['video']) { - bid.vastUrl = idToBidMap[id].adm; - bid.vastUrl = bid.vastUrl.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); - bid.crid = idToBidMap[id].crid; - bid.width = idToImpMap[id].video.w; - bid.height = idToImpMap[id].video.h; - bid.mediaType = 'video'; - } else if (idToImpMap[id]['banner']) { - bid.ad = idToBidMap[id].adm; - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_AD_ID(%7D|\})/gi, idToBidMap[id].adid); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); - bid.width = idToBidMap[id].w; - bid.height = idToBidMap[id].h; - bid.mediaType = 'banner'; - } - bids.push(bid); - } - }); - return bids; -} -function impression(slot) { - return { - id: slot.bidId, - secure: window.location.protocol === 'https:' ? 1 : 0, - 'banner': banner(slot), - 'native': nativeImpression(slot), - 'video': videoImpression(slot), - bidfloor: slot.params.bidFloor || '0.000001', - tagid: slot.params.placementId.toString(), - }; -} - -function banner(slot) { - if (slot.mediaType === 'banner' || utils.deepAccess(slot, 'mediaTypes.banner')) { - const sizes = utils.deepAccess(slot, 'mediaTypes.banner.sizes'); - if (sizes.length > 1) { - let format = []; - for (let f = 0; f < sizes.length; f++) { - format.push({'w': sizes[f][0], 'h': sizes[f][1]}); - } - return {'format': format}; - } else { - return { - w: sizes[0][0], - h: sizes[0][1] - } - } - } - return null; -} - -function videoImpression(slot) { - if (slot.mediaType === 'video' || utils.deepAccess(slot, 'mediaTypes.video')) { - const sizes = utils.deepAccess(slot, 'mediaTypes.video.playerSize'); - const video = { - w: sizes[0][0], - h: sizes[0][1], - mimes: DEFAULT_MIMES, - protocols: DEFAULT_PROTOCOLS, - api: DEFAULT_APIS, - }; - if (slot.params.video) { - Object.keys(slot.params.video).filter(param => includes(VIDEO_TARGETING, param)).forEach(param => video[param] = slot.params.video[param]); - } - return video; - } - return null; -} - -function nativeImpression(slot) { - if (slot.mediaType === 'native' || utils.deepAccess(slot, 'mediaTypes.native')) { - const assets = []; - addAsset(assets, titleAsset(1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN)); - addAsset(assets, dataAsset(2, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN)); - addAsset(assets, dataAsset(3, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN)); - addAsset(assets, imageAsset(4, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN)); - addAsset(assets, imageAsset(5, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN)); - return { - request: JSON.stringify({ assets }), - ver: '1.1', - }; - } - return null; -} - -function addAsset(assets, asset) { - if (asset) { - assets.push(asset); - } -} - -function titleAsset(id, params, defaultLen) { - if (params) { - return { - id, - required: params.required ? 1 : 0, - title: { - len: params.len || defaultLen, - }, - }; - } - return null; -} - -function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { - return params ? { - id, - required: params.required ? 1 : 0, - img: { - type, - wmin: params.wmin || defaultMinWidth, - hmin: params.hmin || defaultMinHeight, - } - } : null; -} - -function dataAsset(id, params, type, defaultLen) { - return params ? { - id, - required: params.required ? 1 : 0, - data: { - type, - len: params.len || defaultLen, - } - } : null; -} - -function site(bidderRequest) { - const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; - const siteId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.siteId : '0'; - const appParams = bidderRequest[0].params.app; - if (!appParams) { - return { - publisher: { - id: pubId.toString(), - domain: window.location.hostname, - }, - id: siteId.toString(), - ref: window.top.document.referrer, - page: window.location.href, - } - } - return null; -} - -function app(bidderRequest) { - const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; - const appParams = bidderRequest[0].params.app; - if (appParams) { - return { - publisher: { - id: pubId.toString(), - }, - id: appParams.id, - name: appParams.name, - bundle: appParams.bundle, - storeurl: appParams.storeUrl, - domain: appParams.domain, - } - } - return null; -} - -function device(bidderRequest) { - const lat = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.latitude : ''; - const lon = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.longitude : ''; - const ifa = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.ifa : ''; - return { - dnt: utils.getDNT() ? 1 : 0, - ua: navigator.userAgent, - language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), - w: (window.screen.width || window.innerWidth), - h: (window.screen.height || window.innerHeigh), - geo: { - lat: lat, - lon: lon, - }, - ifa: ifa, - }; -} - -function parse(rawResponse) { - try { - if (rawResponse) { - return JSON.parse(rawResponse); - } - } catch (ex) { - utils.logError('platformio.parse', 'ERROR', ex); - } - return null; -} - -function applyGdpr(bidderRequest, ortbRequest) { - if (bidderRequest && bidderRequest.gdprConsent) { - ortbRequest.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; - ortbRequest.user = { ext: { consent: bidderRequest.gdprConsent.consentString } }; - } -} - -function nativeResponse(imp, bid) { - if (imp['native']) { - const nativeAd = parse(bid.adm); - const keys = {}; - keys.image = {}; - keys.icon = {}; - if (nativeAd && nativeAd['native'] && nativeAd['native'].assets) { - nativeAd['native'].assets.forEach(asset => { - keys.title = asset.title ? asset.title.text : keys.title; - keys.body = asset.data && asset.id === 2 ? asset.data.value : keys.body; - keys.sponsoredBy = asset.data && asset.id === 3 ? asset.data.value : keys.sponsoredBy; - keys.icon.url = asset.img && asset.id === 4 ? asset.img.url : keys.icon.url; - keys.icon.width = asset.img && asset.id === 4 ? asset.img.w : keys.icon.width; - keys.icon.height = asset.img && asset.id === 4 ? asset.img.h : keys.icon.height; - keys.image.url = asset.img && asset.id === 5 ? asset.img.url : keys.image.url; - keys.image.width = asset.img && asset.id === 5 ? asset.img.w : keys.image.width; - keys.image.height = asset.img && asset.id === 5 ? asset.img.h : keys.image.height; - }); - if (nativeAd['native'].link) { - keys.clickUrl = encodeURIComponent(nativeAd['native'].link.url); - } - return keys; - } - } - return null; -} - -registerBidder(spec); diff --git a/modules/convergeBidAdapter.md b/modules/playwireBidAdapter.md similarity index 54% rename from modules/convergeBidAdapter.md rename to modules/playwireBidAdapter.md index ab916a8b3b6..dddb57c9bc1 100644 --- a/modules/convergeBidAdapter.md +++ b/modules/playwireBidAdapter.md @@ -1,13 +1,13 @@ # Overview -Module Name: Converge Bidder Adapter +Module Name: Playwire Bidder Adapter Module Type: Bidder Adapter -Maintainer: support@converge-digital.com +Maintainer: grid-tech@themediagrid.com # Description -Module that connects to Converge demand source to fetch bids. -Converge Bid Adapter supports Banner and Video (instream and outstream). +Module that connects to Grid demand source to fetch bids. +The adapter is GDPR compliant and supports banner and video (instream and outstream). # Test Parameters ``` @@ -17,10 +17,10 @@ Converge Bid Adapter supports Banner and Video (instream and outstream). sizes: [[300, 250]], bids: [ { - bidder: "converge", + bidder: "playwire", params: { - uid: '59', - priceType: 'gross' // by default is 'net' + uid: '1', + bidFloor: 0.5 } } ] @@ -29,10 +29,9 @@ Converge Bid Adapter supports Banner and Video (instream and outstream). sizes: [[728, 90]], bids: [ { - bidder: "converge", + bidder: "playwire", params: { - uid: 1, - priceType: 'gross', + uid: 2, keywords: { brandsafety: ['disaster'], topic: ['stress', 'fear'] @@ -40,18 +39,23 @@ Converge Bid Adapter supports Banner and Video (instream and outstream). } } ] - },{ + }, + { code: 'test-div', - sizes: [[640, 360]], - mediaTypes: { video: {} }, + sizes: [[728, 90]], + mediaTypes: { video: { + context: 'instream', + playerSize: [728, 90], + mimes: ['video/mp4'] + }, bids: [ { - bidder: "converge", + bidder: "playwire", params: { - uid: 60 + uid: 11 } } ] - } + } ]; ``` diff --git a/modules/prebidServerBidAdapter/config.js b/modules/prebidServerBidAdapter/config.js index 92a8f5deaa5..f6b8ac9f86a 100644 --- a/modules/prebidServerBidAdapter/config.js +++ b/modules/prebidServerBidAdapter/config.js @@ -13,6 +13,15 @@ export const S2S_VENDORS = { }, timeout: 1000 }, + 'appnexuspsp': { + adapter: 'prebidServer', + enabled: true, + endpoint: { + p1Consent: 'https://ib.adnxs.com/openrtb2/prebid', + noP1Consent: 'https://ib.adnxs-simple.com/openrtb2/prebid' + }, + timeout: 1000 + }, 'rubicon': { adapter: 'prebidServer', enabled: true, diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index d91858ed9b2..a4c8f6aae59 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -1,18 +1,22 @@ import Adapter from '../../src/adapter.js'; import { createBid } from '../../src/bidfactory.js'; -import * as utils from '../../src/utils.js'; +import { + getPrebidInternal, logError, isStr, isPlainObject, logWarn, generateUUID, bind, logMessage, + triggerPixel, insertUserSyncIframe, deepAccess, mergeDeep, deepSetValue, cleanObj, parseSizesInput, + getBidRequest, getDefinedParams, createTrackPixelHtml, pick, deepClone, uniques, flatten, isNumber, + isEmpty, isArray, logInfo, timestamp +} from '../../src/utils.js'; import CONSTANTS from '../../src/constants.json'; import adapterManager from '../../src/adapterManager.js'; import { config } from '../../src/config.js'; import { VIDEO, NATIVE } from '../../src/mediaTypes.js'; -import { processNativeAdUnitParams } from '../../src/native.js'; import { isValid } from '../../src/adapters/bidderFactory.js'; import events from '../../src/events.js'; import includes from 'core-js-pure/features/array/includes.js'; import { S2S_VENDORS } from './config.js'; import { ajax } from '../../src/ajax.js'; import find from 'core-js-pure/features/array/find.js'; -import { getPrebidInternal } from '../../src/utils.js'; +import {hook} from '../../src/hook.js'; const getConfig = config.getConfig; @@ -50,8 +54,10 @@ let eidPermissions; * @typedef {Object} S2SDefaultConfig * @summary Base config properties for server to server header bidding * @property {string} [adapter='prebidServer'] adapter code to use for S2S + * @property {boolean} [allowUnknownBidderCodes=false] allow bids from bidders that were not explicitly requested * @property {boolean} [enabled=false] enables S2S bidding * @property {number} [timeout=1000] timeout for S2S bidders - should be lower than `pbjs.requestBids({timeout})` + * @property {number} [syncTimeout=1000] timeout for cookie sync iframe / image rendering * @property {number} [maxBids=1] * @property {AdapterOptions} [adapterOptions] adds arguments to resulting OpenRTB payload to Prebid Server * @property {Object} [syncUrlModifier] @@ -73,8 +79,10 @@ let eidPermissions; */ const s2sDefaultConfig = { timeout: 1000, + syncTimeout: 1000, maxBids: 1, adapter: 'prebidServer', + allowUnknownBidderCodes: false, adapterOptions: {}, syncUrlModifier: {} }; @@ -100,7 +108,7 @@ function updateConfigDefaultVendor(option) { } }); } else { - utils.logError('Incorrect or unavailable prebid server default vendor option: ' + vendor); + logError('Incorrect or unavailable prebid server default vendor option: ' + vendor); return false; } } @@ -116,7 +124,7 @@ function validateConfigRequiredProps(option) { const keys = Object.keys(option); if (['accountId', 'bidders', 'endpoint'].filter(key => { if (!includes(keys, key)) { - utils.logError(key + ' missing in server to server config'); + logError(key + ' missing in server to server config'); return true; } return false; @@ -129,10 +137,17 @@ function validateConfigRequiredProps(option) { // could be removed later as part of a major release, if we decide to not support the old format function formatUrlParams(option) { ['endpoint', 'syncEndpoint'].forEach((prop) => { - if (utils.isStr(option[prop])) { + if (isStr(option[prop])) { let temp = option[prop]; option[prop] = { p1Consent: temp, noP1Consent: temp }; } + if (isPlainObject(option[prop]) && (!option[prop].p1Consent || !option[prop].noP1Consent)) { + ['p1Consent', 'noP1Consent'].forEach((conUrl) => { + if (!option[prop][conUrl]) { + logWarn(`s2sConfig.${prop}.${conUrl} not defined. PBS request will be skipped in some P1 scenarios.`); + } + }); + } }); } @@ -164,7 +179,7 @@ function setS2sConfig(options) { return true; } } - utils.logWarn('prebidServer: s2s config is disabled'); + logWarn('prebidServer: s2s config is disabled'); return false; }); @@ -191,13 +206,13 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { _syncCount++; const payload = { - uuid: utils.generateUUID(), + uuid: generateUUID(), bidders: bidderCodes, account: s2sConfig.accountId }; let userSyncLimit = s2sConfig.userSyncLimit; - if (utils.isNumber(userSyncLimit) && userSyncLimit > 0) { + if (isNumber(userSyncLimit) && userSyncLimit > 0) { payload['limit'] = userSyncLimit; } @@ -225,7 +240,7 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { response = JSON.parse(response); doAllSyncs(response.bidder_status, s2sConfig); } catch (e) { - utils.logError(e); + logError(e); } }, jsonPayload, @@ -245,7 +260,7 @@ function doAllSyncs(bidders, s2sConfig) { // if PBS reports this bidder doesn't have an ID, then call the sync and recurse to the next sync entry if (thisSync.no_cookie) { - doPreBidderSync(thisSync.usersync.type, thisSync.usersync.url, thisSync.bidder, utils.bind.call(doAllSyncs, null, bidders, s2sConfig), s2sConfig); + doPreBidderSync(thisSync.usersync.type, thisSync.usersync.url, thisSync.bidder, bind.call(doAllSyncs, null, bidders, s2sConfig), s2sConfig); } else { // bidder already has an ID, so just recurse to the next sync entry doAllSyncs(bidders, s2sConfig); @@ -263,11 +278,9 @@ function doAllSyncs(bidders, s2sConfig) { */ function doPreBidderSync(type, url, bidder, done, s2sConfig) { if (s2sConfig.syncUrlModifier && typeof s2sConfig.syncUrlModifier[bidder] === 'function') { - const newSyncUrl = s2sConfig.syncUrlModifier[bidder](type, url, bidder); - doBidderSync(type, newSyncUrl, bidder, done) - } else { - doBidderSync(type, url, bidder, done) + url = s2sConfig.syncUrlModifier[bidder](type, url, bidder); } + doBidderSync(type, url, bidder, done, s2sConfig.syncTimeout) } /** @@ -277,19 +290,20 @@ function doPreBidderSync(type, url, bidder, done, s2sConfig) { * @param {string} url the url to sync * @param {string} bidder name of bidder doing sync for * @param {function} done an exit callback; to signify this pixel has either: finished rendering or something went wrong + * @param {number} timeout: maximum time to wait for rendering in milliseconds */ -function doBidderSync(type, url, bidder, done) { +function doBidderSync(type, url, bidder, done, timeout) { if (!url) { - utils.logError(`No sync url for bidder "${bidder}": ${url}`); + logError(`No sync url for bidder "${bidder}": ${url}`); done(); } else if (type === 'image' || type === 'redirect') { - utils.logMessage(`Invoking image pixel user sync for bidder: "${bidder}"`); - utils.triggerPixel(url, done); - } else if (type == 'iframe') { - utils.logMessage(`Invoking iframe user sync for bidder: "${bidder}"`); - utils.insertUserSyncIframe(url, done); + logMessage(`Invoking image pixel user sync for bidder: "${bidder}"`); + triggerPixel(url, done, timeout); + } else if (type === 'iframe') { + logMessage(`Invoking iframe user sync for bidder: "${bidder}"`); + insertUserSyncIframe(url, done, timeout); } else { - utils.logError(`User sync type "${type}" not supported for bidder: "${bidder}"`); + logError(`User sync type "${type}" not supported for bidder: "${bidder}"`); done(); } } @@ -305,7 +319,7 @@ function doClientSideSyncs(bidders, gdprConsent, uspConsent) { if (clientAdapter && clientAdapter.registerSyncs) { config.runWithBidder( bidder, - utils.bind.call( + bind.call( clientAdapter.registerSyncs, clientAdapter, [], @@ -326,12 +340,12 @@ function _appendSiteAppDevice(request, pageUrl, accountId) { request.app.publisher = {id: accountId} } else { request.site = {}; - if (utils.isPlainObject(config.getConfig('site'))) { + if (isPlainObject(config.getConfig('site'))) { request.site = config.getConfig('site'); } // set publisher.id if not already defined - if (!utils.deepAccess(request.site, 'publisher.id')) { - utils.deepSetValue(request.site, 'publisher.id', accountId); + if (!deepAccess(request.site, 'publisher.id')) { + deepSetValue(request.site, 'publisher.id', accountId); } // set site.page if not already defined if (!request.site.page) { @@ -357,13 +371,7 @@ function addBidderFirstPartyDataToRequest(request) { const fpdConfigs = Object.keys(bidderConfig).reduce((acc, bidder) => { const currBidderConfig = bidderConfig[bidder]; if (currBidderConfig.ortb2) { - const ortb2 = {}; - if (currBidderConfig.ortb2.site) { - ortb2.site = currBidderConfig.ortb2.site; - } - if (currBidderConfig.ortb2.user) { - ortb2.user = currBidderConfig.ortb2.user; - } + const ortb2 = mergeDeep({}, currBidderConfig.ortb2); acc.push({ bidders: [ bidder ], @@ -374,7 +382,7 @@ function addBidderFirstPartyDataToRequest(request) { }, []); if (fpdConfigs.length) { - utils.deepSetValue(request, 'ext.prebid.bidderconfig', fpdConfigs); + deepSetValue(request, 'ext.prebid.bidderconfig', fpdConfigs); } } @@ -427,7 +435,6 @@ let nativeEventTrackerMethodMap = { * Protocol spec for OpenRTB endpoint * e.g., https:///v1/openrtb2/auction */ -let bidIdMap = {}; let nativeAssetCache = {}; // store processed native params to preserve /** @@ -441,13 +448,13 @@ let wurlMap = {}; * @param {string} wurl events.winurl passed from prebidServer as wurl */ function addWurl(auctionId, adId, wurl) { - if ([auctionId, adId].every(utils.isStr)) { + if ([auctionId, adId].every(isStr)) { wurlMap[`${auctionId}${adId}`] = wurl; } } function getPbsResponseData(bidderRequests, response, pbsName, pbjsName) { - const bidderValues = utils.deepAccess(response, `ext.${pbsName}`); + const bidderValues = deepAccess(response, `ext.${pbsName}`); if (bidderValues) { Object.keys(bidderValues).forEach(bidder => { let biddersReq = find(bidderRequests, bidderReq => bidderReq.bidderCode === bidder); @@ -463,7 +470,7 @@ function getPbsResponseData(bidderRequests, response, pbsName, pbjsName) { * @param {string} adId generated value set to bidObject.adId by bidderFactory Bid() */ function removeWurl(auctionId, adId) { - if ([auctionId, adId].every(utils.isStr)) { + if ([auctionId, adId].every(isStr)) { wurlMap[`${auctionId}${adId}`] = undefined; } } @@ -473,7 +480,7 @@ function removeWurl(auctionId, adId) { * @return {(string|undefined)} events.winurl which was passed as wurl */ function getWurl(auctionId, adId) { - if ([auctionId, adId].every(utils.isStr)) { + if ([auctionId, adId].every(isStr)) { return wurlMap[`${auctionId}${adId}`]; } } @@ -485,47 +492,79 @@ export function resetWurlMap() { wurlMap = {}; } -const OPEN_RTB_PROTOCOL = { - buildRequest(s2sBidRequest, bidRequests, adUnits, s2sConfig, requestedBidders) { +function ORTB2(s2sBidRequest, bidderRequests, adUnits, requestedBidders) { + this.s2sBidRequest = s2sBidRequest; + this.bidderRequests = bidderRequests; + this.adUnits = adUnits; + this.s2sConfig = s2sBidRequest.s2sConfig; + this.requestedBidders = requestedBidders; + + this.bidIdMap = {}; + this.adUnitsByImp = {}; + this.impRequested = {}; + this.auctionId = bidderRequests.map(br => br.auctionId).reduce((l, r) => (l == null || l === r) && r); + this.requestTimestamp = timestamp(); +} + +Object.assign(ORTB2.prototype, { + buildRequest() { + const {s2sBidRequest, bidderRequests: bidRequests, adUnits, s2sConfig, requestedBidders} = this; + let imps = []; let aliases = {}; const firstBidRequest = bidRequests[0]; // transform ad unit into array of OpenRTB impression objects + let impIds = new Set(); adUnits.forEach(adUnit => { - const nativeParams = processNativeAdUnitParams(utils.deepAccess(adUnit, 'mediaTypes.native')); + // in case there is a duplicate imp.id, add '-2' suffix to the second imp.id. + // e.g. if there are 2 adUnits (case of twin adUnit codes) with code 'test', + // first imp will have id 'test' and second imp will have id 'test-2' + let impressionId = adUnit.code; + let i = 1; + while (impIds.has(impressionId)) { + i++; + impressionId = `${adUnit.code}-${i}`; + } + impIds.add(impressionId); + this.adUnitsByImp[impressionId] = adUnit; + + const nativeParams = adUnit.nativeParams; let nativeAssets; if (nativeParams) { try { - nativeAssets = nativeAssetCache[adUnit.code] = Object.keys(nativeParams).reduce((assets, type) => { + nativeAssets = nativeAssetCache[impressionId] = Object.keys(nativeParams).reduce((assets, type) => { let params = nativeParams[type]; function newAsset(obj) { return Object.assign({ required: params.required ? 1 : 0 - }, obj ? utils.cleanObj(obj) : {}); + }, obj ? cleanObj(obj) : {}); } switch (type) { case 'image': case 'icon': let imgTypeId = nativeImgIdMap[type]; - let asset = utils.cleanObj({ + let asset = cleanObj({ type: imgTypeId, - w: utils.deepAccess(params, 'sizes.0'), - h: utils.deepAccess(params, 'sizes.1'), - wmin: utils.deepAccess(params, 'aspect_ratios.0.min_width'), - hmin: utils.deepAccess(params, 'aspect_ratios.0.min_height') + w: deepAccess(params, 'sizes.0'), + h: deepAccess(params, 'sizes.1'), + wmin: deepAccess(params, 'aspect_ratios.0.min_width'), + hmin: deepAccess(params, 'aspect_ratios.0.min_height') }); if (!((asset.w && asset.h) || (asset.hmin && asset.wmin))) { throw 'invalid img sizes (must provide sizes or min_height & min_width if using aspect_ratios)'; } if (Array.isArray(params.aspect_ratios)) { // pass aspect_ratios as ext data I guess? - asset.ext = { - aspectratios: params.aspect_ratios.map( - ratio => `${ratio.ratio_width}:${ratio.ratio_height}` - ) + const aspectRatios = params.aspect_ratios + .filter((ar) => ar.ratio_width && ar.ratio_height) + .map(ratio => `${ratio.ratio_width}:${ratio.ratio_height}`); + if (aspectRatios.length > 0) { + asset.ext = { + aspectratios: aspectRatios + } } } assets.push(newAsset({ @@ -556,17 +595,14 @@ const OPEN_RTB_PROTOCOL = { return assets; }, []); } catch (e) { - utils.logError('error creating native request: ' + String(e)) + logError('error creating native request: ' + String(e)) } } - const videoParams = utils.deepAccess(adUnit, 'mediaTypes.video'); - const bannerParams = utils.deepAccess(adUnit, 'mediaTypes.banner'); + const videoParams = deepAccess(adUnit, 'mediaTypes.video'); + const bannerParams = deepAccess(adUnit, 'mediaTypes.banner'); adUnit.bids.forEach(bid => { - // OpenRTB response contains the adunit code and bidder name. These are - // combined to create a unique key for each bid since an id isn't returned - bidIdMap[`${adUnit.code}${bid.bidder}`] = bid.bid_id; - + this.setBidRequestId(impressionId, bid.bidder, bid.bid_id); // check for and store valid aliases to add to the request if (adapterManager.aliasRegistry[bid.bidder]) { const bidder = adapterManager.bidderRegistry[bid.bidder]; @@ -580,7 +616,7 @@ const OPEN_RTB_PROTOCOL = { let mediaTypes = {}; if (bannerParams && bannerParams.sizes) { - const sizes = utils.parseSizesInput(bannerParams.sizes); + const sizes = parseSizesInput(bannerParams.sizes); // get banner sizes in form [{ w: , h: }, ...] const format = sizes.map(size => { @@ -591,17 +627,29 @@ const OPEN_RTB_PROTOCOL = { }); mediaTypes['banner'] = {format}; + + if (bannerParams.pos) mediaTypes['banner'].pos = bannerParams.pos; } - if (!utils.isEmpty(videoParams)) { - if (videoParams.context === 'outstream' && !adUnit.renderer) { + if (!isEmpty(videoParams)) { + if (videoParams.context === 'outstream' && !videoParams.renderer && !adUnit.renderer) { // Don't push oustream w/o renderer to request object. - utils.logError('Outstream bid without renderer cannot be sent to Prebid Server.'); + logError('Outstream bid without renderer cannot be sent to Prebid Server.'); } else { if (videoParams.context === 'instream' && !videoParams.hasOwnProperty('placement')) { videoParams.placement = 1; } - mediaTypes['video'] = videoParams; + + mediaTypes['video'] = Object.keys(videoParams).filter(param => param !== 'context') + .reduce((result, param) => { + if (param === 'playerSize') { + result.w = deepAccess(videoParams, `${param}.0.0`); + result.h = deepAccess(videoParams, `${param}.0.1`); + } else { + result[param] = videoParams[param]; + } + return result; + }, {}); } } @@ -622,7 +670,7 @@ const OPEN_RTB_PROTOCOL = { ver: '1.2' } } catch (e) { - utils.logError('error creating native request: ' + String(e)) + logError('error creating native request: ' + String(e)) } } @@ -631,15 +679,15 @@ const OPEN_RTB_PROTOCOL = { const ext = adUnit.bids.reduce((acc, bid) => { const adapter = adapterManager.bidderRegistry[bid.bidder]; if (adapter && adapter.getSpec().transformBidParams) { - bid.params = adapter.getSpec().transformBidParams(bid.params, true); + bid.params = adapter.getSpec().transformBidParams(bid.params, true, adUnit, bidRequests); } acc[bid.bidder] = (s2sConfig.adapterOptions && s2sConfig.adapterOptions[bid.bidder]) ? Object.assign({}, bid.params, s2sConfig.adapterOptions[bid.bidder]) : bid.params; return acc; - }, {...utils.deepAccess(adUnit, 'ortb2Imp.ext')}); + }, {...deepAccess(adUnit, 'ortb2Imp.ext')}); - const imp = { id: adUnit.code, ext, secure: s2sConfig.secure }; + const imp = { id: impressionId, ext, secure: s2sConfig.secure }; - const ortb2 = {...utils.deepAccess(adUnit, 'ortb2Imp.ext.data')}; + const ortb2 = {...deepAccess(adUnit, 'ortb2Imp.ext.data')}; Object.keys(ortb2).forEach(prop => { /** * Prebid AdSlot @@ -647,7 +695,7 @@ const OPEN_RTB_PROTOCOL = { */ if (prop === 'pbadslot') { if (typeof ortb2[prop] === 'string' && ortb2[prop]) { - utils.deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); + deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); } else { // remove pbadslot property if it doesn't meet the spec delete imp.ext.data.pbadslot; @@ -658,13 +706,13 @@ const OPEN_RTB_PROTOCOL = { */ ['name', 'adslot'].forEach(name => { /** @type {(string|undefined)} */ - const value = utils.deepAccess(ortb2, `adserver.${name}`); + const value = deepAccess(ortb2, `adserver.${name}`); if (typeof value === 'string' && value) { - utils.deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); + deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); } }); } else { - utils.deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); + deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); } }); @@ -673,7 +721,7 @@ const OPEN_RTB_PROTOCOL = { // if storedAuctionResponse has been set, pass SRID const storedAuctionResponseBid = find(firstBidRequest.bids, bid => (bid.adUnitCode === adUnit.code && bid.storedAuctionResponse)); if (storedAuctionResponseBid) { - utils.deepSetValue(imp, 'ext.prebid.storedauctionresponse.id', storedAuctionResponseBid.storedAuctionResponse.toString()); + deepSetValue(imp, 'ext.prebid.storedauctionresponse.id', storedAuctionResponseBid.storedAuctionResponse.toString()); } const getFloorBid = find(firstBidRequest.bids, bid => bid.adUnitCode === adUnit.code && typeof bid.getFloor === 'function'); @@ -685,7 +733,7 @@ const OPEN_RTB_PROTOCOL = { currency: config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY, }); } catch (e) { - utils.logError('PBS: getFloor threw an error: ', e); + logError('PBS: getFloor threw an error: ', e); } if (floorInfo && floorInfo.currency && !isNaN(parseFloat(floorInfo.floor))) { imp.bidfloor = parseFloat(floorInfo.floor); @@ -699,15 +747,16 @@ const OPEN_RTB_PROTOCOL = { }); if (!imps.length) { - utils.logError('Request to Prebid Server rejected due to invalid media type(s) in adUnit.'); + logError('Request to Prebid Server rejected due to invalid media type(s) in adUnit.'); return; } const request = { - id: s2sBidRequest.tid, + id: firstBidRequest.auctionId, source: {tid: s2sBidRequest.tid}, tmax: s2sConfig.timeout, imp: imps, - test: getConfig('debug') ? 1 : 0, + // to do: add setconfig option to pass test = 1 + test: 0, ext: { prebid: { // set ext.prebid.auctiontimestamp with the auction timestamp. Data type is long integer. @@ -722,9 +771,17 @@ const OPEN_RTB_PROTOCOL = { } }; + // This is no longer overwritten unless name and version explicitly overwritten by extPrebid (mergeDeep) + request.ext.prebid = Object.assign(request.ext.prebid, {channel: {name: 'pbjs', version: $$PREBID_GLOBAL$$.version}}) + + // set debug flag if in debug mode + if (getConfig('debug')) { + request.ext.prebid = Object.assign(request.ext.prebid, {debug: true}) + } + // s2sConfig video.ext.prebid is passed through openrtb to PBS if (s2sConfig.extPrebid && typeof s2sConfig.extPrebid === 'object') { - request.ext.prebid = Object.assign(request.ext.prebid, s2sConfig.extPrebid); + request.ext.prebid = mergeDeep(request.ext.prebid, s2sConfig.extPrebid); } /** @@ -742,36 +799,36 @@ const OPEN_RTB_PROTOCOL = { _appendSiteAppDevice(request, bidRequests[0].refererInfo.referer, s2sConfig.accountId); // pass schain object if it is present - const schain = utils.deepAccess(bidRequests, '0.bids.0.schain'); + const schain = deepAccess(bidRequests, '0.bids.0.schain'); if (schain) { request.source.ext = { schain: schain }; } - if (!utils.isEmpty(aliases)) { + if (!isEmpty(aliases)) { request.ext.prebid.aliases = {...request.ext.prebid.aliases, ...aliases}; } - const bidUserIdAsEids = utils.deepAccess(bidRequests, '0.bids.0.userIdAsEids'); - if (utils.isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { - utils.deepSetValue(request, 'user.ext.eids', bidUserIdAsEids); + const bidUserIdAsEids = deepAccess(bidRequests, '0.bids.0.userIdAsEids'); + if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + deepSetValue(request, 'user.ext.eids', bidUserIdAsEids); } - if (utils.isArray(eidPermissions) && eidPermissions.length > 0) { - if (requestedBidders && utils.isArray(requestedBidders)) { + if (isArray(eidPermissions) && eidPermissions.length > 0) { + if (requestedBidders && isArray(requestedBidders)) { eidPermissions.forEach(i => { if (i.bidders) { - i.bidders = i.bidders.filter(bidder => requestedBidders.includes(bidder)) + i.bidders = i.bidders.filter(bidder => includes(requestedBidders, bidder)) } }); } - utils.deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions); + deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions); } const multibid = config.getConfig('multibid'); if (multibid) { - utils.deepSetValue(request, 'ext.prebid.multibid', multibid.reduce((result, i) => { + deepSetValue(request, 'ext.prebid.multibid', multibid.reduce((result, i) => { let obj = {}; Object.keys(i).forEach(key => { @@ -791,36 +848,34 @@ const OPEN_RTB_PROTOCOL = { if (typeof firstBidRequest.gdprConsent.gdprApplies === 'boolean') { gdprApplies = firstBidRequest.gdprConsent.gdprApplies ? 1 : 0; } - utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); - utils.deepSetValue(request, 'user.ext.consent', firstBidRequest.gdprConsent.consentString); + deepSetValue(request, 'regs.ext.gdpr', gdprApplies); + deepSetValue(request, 'user.ext.consent', firstBidRequest.gdprConsent.consentString); if (firstBidRequest.gdprConsent.addtlConsent && typeof firstBidRequest.gdprConsent.addtlConsent === 'string') { - utils.deepSetValue(request, 'user.ext.ConsentedProvidersSettings.consented_providers', firstBidRequest.gdprConsent.addtlConsent); + deepSetValue(request, 'user.ext.ConsentedProvidersSettings.consented_providers', firstBidRequest.gdprConsent.addtlConsent); } } // US Privacy (CCPA) support if (firstBidRequest.uspConsent) { - utils.deepSetValue(request, 'regs.ext.us_privacy', firstBidRequest.uspConsent); + deepSetValue(request, 'regs.ext.us_privacy', firstBidRequest.uspConsent); } } if (getConfig('coppa') === true) { - utils.deepSetValue(request, 'regs.coppa', 1); + deepSetValue(request, 'regs.coppa', 1); } const commonFpd = getConfig('ortb2') || {}; - if (commonFpd.site) { - utils.mergeDeep(request, {site: commonFpd.site}); - } - if (commonFpd.user) { - utils.mergeDeep(request, {user: commonFpd.user}); - } + mergeDeep(request, commonFpd); + addBidderFirstPartyDataToRequest(request); + request.imp.forEach((imp) => this.impRequested[imp.id] = imp); return request; }, - interpretResponse(response, bidderRequests, s2sConfig) { + interpretResponse(response) { + const {bidderRequests, s2sConfig} = this; const bids = []; [['errors', 'serverErrors'], ['responsetimemillis', 'serverResponseTimeMs']] @@ -830,51 +885,51 @@ const OPEN_RTB_PROTOCOL = { // a seatbid object contains a `bid` array and a `seat` string response.seatbid.forEach(seatbid => { (seatbid.bid || []).forEach(bid => { - let bidRequest; - let key = `${bid.impid}${seatbid.seat}`; - if (bidIdMap[key]) { - bidRequest = utils.getBidRequest( - bidIdMap[key], - bidderRequests - ); + const bidRequest = this.getBidRequest(bid.impid, seatbid.seat); + if (bidRequest == null && !s2sConfig.allowUnknownBidderCodes) { + logWarn(`PBS adapter received bid from unknown bidder (${seatbid.seat}), but 's2sConfig.allowUnknownBidderCodes' is not set. Ignoring bid.`); + return; } const cpm = bid.price; const status = cpm !== 0 ? CONSTANTS.STATUS.GOOD : CONSTANTS.STATUS.NO_BID; - let bidObject = createBid(status, bidRequest || { + let bidObject = createBid(status, { bidder: seatbid.seat, - src: TYPE + src: TYPE, + bidId: bidRequest ? (bidRequest.bidId || bidRequest.bid_Id) : null, + transactionId: this.adUnitsByImp[bid.impid].transactionId, + auctionId: this.auctionId, }); - + bidObject.requestTimestamp = this.requestTimestamp; bidObject.cpm = cpm; // temporarily leaving attaching it to each bidResponse so no breaking change // BUT: this is a flat map, so it should be only attached to bidderRequest, a the change above does - let serverResponseTimeMs = utils.deepAccess(response, ['ext', 'responsetimemillis', seatbid.seat].join('.')); + let serverResponseTimeMs = deepAccess(response, ['ext', 'responsetimemillis', seatbid.seat].join('.')); if (bidRequest && serverResponseTimeMs) { bidRequest.serverResponseTimeMs = serverResponseTimeMs; } // Look for seatbid[].bid[].ext.prebid.bidid and place it in the bidResponse object for use in analytics adapters as 'pbsBidId' - const bidId = utils.deepAccess(bid, 'ext.prebid.bidid'); - if (utils.isStr(bidId)) { + const bidId = deepAccess(bid, 'ext.prebid.bidid'); + if (isStr(bidId)) { bidObject.pbsBidId = bidId; } // store wurl by auctionId and adId so it can be accessed from the BID_WON event handler - if (utils.isStr(utils.deepAccess(bid, 'ext.prebid.events.win'))) { - addWurl(bidRequest.auctionId, bidObject.adId, utils.deepAccess(bid, 'ext.prebid.events.win')); + if (isStr(deepAccess(bid, 'ext.prebid.events.win'))) { + addWurl(this.auctionId, bidObject.adId, deepAccess(bid, 'ext.prebid.events.win')); } - let extPrebidTargeting = utils.deepAccess(bid, 'ext.prebid.targeting'); + let extPrebidTargeting = deepAccess(bid, 'ext.prebid.targeting'); // If ext.prebid.targeting exists, add it as a property value named 'adserverTargeting' // The removal of hb_winurl and hb_bidid targeting values is temporary // once we get through the transition, this block will be removed. - if (utils.isPlainObject(extPrebidTargeting)) { + if (isPlainObject(extPrebidTargeting)) { // If wurl exists, remove hb_winurl and hb_bidid targeting attributes - if (utils.isStr(utils.deepAccess(bid, 'ext.prebid.events.win'))) { - extPrebidTargeting = utils.getDefinedParams(extPrebidTargeting, Object.keys(extPrebidTargeting) + if (isStr(deepAccess(bid, 'ext.prebid.events.win'))) { + extPrebidTargeting = getDefinedParams(extPrebidTargeting, Object.keys(extPrebidTargeting) .filter(i => (i.indexOf('hb_winurl') === -1 && i.indexOf('hb_bidid') === -1))); } bidObject.adserverTargeting = extPrebidTargeting; @@ -882,11 +937,10 @@ const OPEN_RTB_PROTOCOL = { bidObject.seatBidId = bid.id; - if (utils.deepAccess(bid, 'ext.prebid.type') === VIDEO) { + if (deepAccess(bid, 'ext.prebid.type') === VIDEO) { bidObject.mediaType = VIDEO; - let sizes = bidRequest.sizes && bidRequest.sizes[0]; - bidObject.playerWidth = sizes[0]; - bidObject.playerHeight = sizes[1]; + const impReq = this.impRequested[bid.impid]; + [bidObject.playerWidth, bidObject.playerHeight] = [impReq.video.w, impReq.video.h]; // try to get cache values from 'response.ext.prebid.cache.js' // else try 'bid.ext.prebid.targeting' as fallback @@ -901,7 +955,7 @@ const OPEN_RTB_PROTOCOL = { if (bid.adm) { bidObject.vastXml = bid.adm; } if (!bidObject.vastUrl && bid.nurl) { bidObject.vastUrl = bid.nurl; } - } else if (utils.deepAccess(bid, 'ext.prebid.type') === NATIVE) { + } else if (deepAccess(bid, 'ext.prebid.type') === NATIVE) { bidObject.mediaType = NATIVE; let adm; if (typeof bid.adm === 'string') { @@ -927,18 +981,18 @@ const OPEN_RTB_PROTOCOL = { }); } - if (utils.isPlainObject(adm) && Array.isArray(adm.assets)) { - let origAssets = nativeAssetCache[bidRequest.adUnitCode]; - bidObject.native = utils.cleanObj(adm.assets.reduce((native, asset) => { + if (isPlainObject(adm) && Array.isArray(adm.assets)) { + let origAssets = nativeAssetCache[bid.impid]; + bidObject.native = cleanObj(adm.assets.reduce((native, asset) => { let origAsset = origAssets[asset.id]; - if (utils.isPlainObject(asset.img)) { - native[origAsset.img.type ? nativeImgIdMap[origAsset.img.type] : 'image'] = utils.pick( + if (isPlainObject(asset.img)) { + native[origAsset.img.type ? nativeImgIdMap[origAsset.img.type] : 'image'] = pick( asset.img, ['url', 'w as width', 'h as height'] ); - } else if (utils.isPlainObject(asset.title)) { + } else if (isPlainObject(asset.title)) { native['title'] = asset.title.text - } else if (utils.isPlainObject(asset.data)) { + } else if (isPlainObject(asset.data)) { nativeDataNames.forEach(dataType => { if (nativeDataIdMap[dataType] === origAsset.data.type) { native[dataType] = asset.data.value; @@ -946,19 +1000,19 @@ const OPEN_RTB_PROTOCOL = { }); } return native; - }, utils.cleanObj({ + }, cleanObj({ clickUrl: adm.link, - clickTrackers: utils.deepAccess(adm, 'link.clicktrackers'), + clickTrackers: deepAccess(adm, 'link.clicktrackers'), impressionTrackers: trackers[nativeEventTrackerMethodMap.img], javascriptTrackers: trackers[nativeEventTrackerMethodMap.js] }))); } else { - utils.logError('prebid server native response contained no assets'); + logError('prebid server native response contained no assets'); } } else { // banner if (bid.adm && bid.nurl) { bidObject.ad = bid.adm; - bidObject.ad += utils.createTrackPixelHtml(decodeURIComponent(bid.nurl)); + bidObject.ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); } else if (bid.adm) { bidObject.ad = bid.adm; } else if (bid.nurl) { @@ -969,13 +1023,13 @@ const OPEN_RTB_PROTOCOL = { bidObject.width = bid.w; bidObject.height = bid.h; if (bid.dealid) { bidObject.dealId = bid.dealid; } - bidObject.requestId = bidRequest.bidId || bidRequest.bid_Id; bidObject.creative_id = bid.crid; bidObject.creativeId = bid.crid; if (bid.burl) { bidObject.burl = bid.burl; } bidObject.currency = (response.cur) ? response.cur : DEFAULT_S2S_CURRENCY; - bidObject.meta = bidObject.meta || {}; - if (bid.ext && bid.ext.dchain) { bidObject.meta.dchain = utils.deepClone(bid.ext.dchain); } + bidObject.meta = {}; + let extPrebidMeta = deepAccess(bid, 'ext.prebid.meta'); + if (extPrebidMeta && isPlainObject(extPrebidMeta)) { bidObject.meta = deepClone(extPrebidMeta); } if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } // the OpenRTB location for "TTL" as understood by Prebid.js is "exp" (expiration). @@ -983,14 +1037,24 @@ const OPEN_RTB_PROTOCOL = { bidObject.ttl = (bid.exp) ? bid.exp : configTtl; bidObject.netRevenue = (bid.netRevenue) ? bid.netRevenue : DEFAULT_S2S_NETREVENUE; - bids.push({ adUnit: bid.impid, bid: bidObject }); + bids.push({ adUnit: this.adUnitsByImp[bid.impid].code, bid: bidObject }); }); }); } return bids; + }, + setBidRequestId(impId, bidderCode, bidId) { + this.bidIdMap[this.impBidderKey(impId, bidderCode)] = bidId; + }, + getBidRequest(impId, bidderCode) { + const key = this.impBidderKey(impId, bidderCode); + return this.bidIdMap[key] && getBidRequest(this.bidIdMap[key], this.bidderRequests); + }, + impBidderKey(impId, bidderCode) { + return `${impId}${bidderCode}`; } -}; +}); /** * BID_WON event to request the wurl @@ -998,9 +1062,9 @@ const OPEN_RTB_PROTOCOL = { */ function bidWonHandler(bid) { const wurl = getWurl(bid.auctionId, bid.adId); - if (utils.isStr(wurl)) { - utils.logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`); - utils.triggerPixel(wurl); + if (isStr(wurl)) { + logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`); + triggerPixel(wurl); // remove from wurl cache, since the wurl url was called removeWurl(bid.auctionId, bid.adId); @@ -1011,7 +1075,7 @@ function hasPurpose1Consent(gdprConsent) { let result = true; if (gdprConsent) { if (gdprConsent.gdprApplies && gdprConsent.apiVersion === 2) { - result = !!(utils.deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true); + result = !!(deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true); } } return result; @@ -1038,20 +1102,8 @@ export function PrebidServer() { /* Prebid executes this function when the page asks to send out bid requests */ baseAdapter.callBids = function(s2sBidRequest, bidRequests, addBidResponse, done, ajax) { - const adUnits = utils.deepClone(s2sBidRequest.ad_units); let { gdprConsent, uspConsent } = getConsentData(bidRequests); - // at this point ad units should have a size array either directly or mapped so filter for that - const validAdUnits = adUnits.filter(unit => - unit.mediaTypes && (unit.mediaTypes.native || (unit.mediaTypes.banner && unit.mediaTypes.banner.sizes) || (unit.mediaTypes.video && unit.mediaTypes.video.playerSize)) - ); - - // in case config.bidders contains invalid bidders, we only process those we sent requests for - const requestedBidders = validAdUnits - .map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(utils.uniques)) - .reduce(utils.flatten) - .filter(utils.uniques); - if (Array.isArray(_s2sConfigs)) { if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint && getMatchingConsentUrl(s2sBidRequest.s2sConfig.syncEndpoint, gdprConsent)) { let syncBidders = s2sBidRequest.s2sConfig.bidders @@ -1061,56 +1113,23 @@ export function PrebidServer() { queueSync(syncBidders, gdprConsent, uspConsent, s2sBidRequest.s2sConfig); } - const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits, s2sBidRequest.s2sConfig, requestedBidders); - const requestJson = request && JSON.stringify(request); - utils.logInfo('BidRequest: ' + requestJson); - if (request && requestJson) { - ajax( - getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent), - { - success: response => handleResponse(response, requestedBidders, bidRequests, addBidResponse, done, s2sBidRequest.s2sConfig), - error: done - }, - requestJson, - { contentType: 'text/plain', withCredentials: true } - ); - } - } - }; - - /* Notify Prebid of bid responses so bids can get in the auction */ - function handleResponse(response, requestedBidders, bidderRequests, addBidResponse, done, s2sConfig) { - let result; - let bids = []; - let { gdprConsent, uspConsent } = getConsentData(bidderRequests); - - try { - result = JSON.parse(response); - - bids = OPEN_RTB_PROTOCOL.interpretResponse( - result, - bidderRequests, - s2sConfig - ); - - bids.forEach(({adUnit, bid}) => { - if (isValid(adUnit, bid, bidderRequests)) { - addBidResponse(adUnit, bid); + processPBSRequest(s2sBidRequest, bidRequests, ajax, { + onResponse: function (isValid, requestedBidders) { + if (isValid) { + bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); + } + done(); + doClientSideSyncs(requestedBidders, gdprConsent, uspConsent); + }, + onError: done, + onBid: function ({adUnit, bid}) { + if (isValid(adUnit, bid)) { + addBidResponse(adUnit, bid); + } } - }); - - bidderRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); - } catch (error) { - utils.logError(error); - } - - if (!result || (result.status && includes(result.status, 'Error'))) { - utils.logError('error parsing response: ', result.status); + }) } - - done(); - doClientSideSyncs(requestedBidders, gdprConsent, uspConsent); - } + }; // Listen for bid won to call wurl events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler); @@ -1122,6 +1141,66 @@ export function PrebidServer() { }); } +/** + * Build and send the appropriate HTTP request over the network, then interpret the response. + * @param s2sBidRequest + * @param bidRequests + * @param ajax + * @param onResponse {function(boolean, Array[String])} invoked on a successful HTTP response - with a flag indicating whether it was successful, + * and a list of the unique bidder codes that were sent in the request + * @param onError {function(String, {})} invoked on HTTP failure - with status message and XHR error + * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse + */ +export const processPBSRequest = hook('sync', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid}) { + let { gdprConsent } = getConsentData(bidRequests); + const adUnits = deepClone(s2sBidRequest.ad_units); + + // at this point ad units should have a size array either directly or mapped so filter for that + const validAdUnits = adUnits.filter(unit => + unit.mediaTypes && (unit.mediaTypes.native || (unit.mediaTypes.banner && unit.mediaTypes.banner.sizes) || (unit.mediaTypes.video && unit.mediaTypes.video.playerSize)) + ); + + // in case config.bidders contains invalid bidders, we only process those we sent requests for + const requestedBidders = validAdUnits + .map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(uniques)) + .reduce(flatten) + .filter(uniques); + + const ortb2 = new ORTB2(s2sBidRequest, bidRequests, validAdUnits, requestedBidders); + const request = ortb2.buildRequest(); + const requestJson = request && JSON.stringify(request); + logInfo('BidRequest: ' + requestJson); + const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent); + if (request && requestJson && endpointUrl) { + ajax( + endpointUrl, + { + success: function (response) { + let result; + try { + result = JSON.parse(response); + const bids = ortb2.interpretResponse(result); + bids.forEach(onBid); + } catch (error) { + logError(error); + } + if (!result || (result.status && includes(result.status, 'Error'))) { + logError('error parsing response: ', result ? result.status : 'not valid JSON'); + onResponse(false, requestedBidders); + } else { + onResponse(true, requestedBidders); + } + }, + error: onError + }, + requestJson, + {contentType: 'text/plain', withCredentials: true} + ); + } else { + logError('PBS request not made. Check endpoints.'); + } +}, 'processPBSRequest'); + /** * Global setter that sets eids permissions for bidders * This setter is to be used by userId module when included diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index b9a7d79f991..a1a0a636e3c 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -1,3 +1,4 @@ +import { generateUUID, getParameterByName, logError, parseUrl, logInfo } from '../src/utils.js'; import {ajaxBuilder} from '../src/ajax.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; @@ -11,13 +12,12 @@ const DEFAULT_EVENT_URL = 'https://endpoint.prebidmanager.com/endpoint' const analyticsType = 'endpoint'; const analyticsName = 'Prebid Manager Analytics: '; -var utils = require('../src/utils.js'); var CONSTANTS = require('../src/constants.json'); let ajax = ajaxBuilder(0); var _VERSION = 1; var initOptions = null; -var _pageViewId = utils.generateUUID(); +var _pageViewId = generateUUID(); var _startAuction = 0; var _bidRequestTimeout = 0; let flushInterval; @@ -76,7 +76,7 @@ function collectUtmTagData() { let pmUtmTags = {}; try { utmTags.forEach(function (utmKey) { - let utmValue = utils.getParameterByName(utmKey); + let utmValue = getParameterByName(utmKey); if (utmValue !== '') { newUtm = true; } @@ -95,7 +95,7 @@ function collectUtmTagData() { }); } } catch (e) { - utils.logError(`${analyticsName}Error`, e); + logError(`${analyticsName}Error`, e); pmUtmTags['error_utm'] = 1; } return pmUtmTags; @@ -106,7 +106,7 @@ function collectPageInfo() { domain: window.location.hostname, } if (document.referrer) { - pageInfo.referrerDomain = utils.parseUrl(document.referrer).hostname; + pageInfo.referrerDomain = parseUrl(document.referrer).hostname; } return pageInfo; } @@ -128,7 +128,7 @@ function flush() { ajax( initOptions.url, - () => utils.logInfo(`${analyticsName} sent events batch`), + () => logInfo(`${analyticsName} sent events batch`), _VERSION + ':' + JSON.stringify(data), { contentType: 'text/plain', @@ -215,7 +215,7 @@ function handleEvent(eventType, eventArgs) { function sendEvent(event) { _eventQueue.push(event); - utils.logInfo(`${analyticsName}Event ${event.eventType}:`, event); + logInfo(`${analyticsName}Event ${event.eventType}:`, event); if (event.eventType === CONSTANTS.EVENTS.AUCTION_END) { flush(); diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 3555fedbf3a..5bca25671c0 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -1,6 +1,6 @@ +import { parseUrl, deepAccess, parseGPTSingleSizeArray, getGptSlotInfoForAdUnitCode, deepSetValue, logWarn, deepClone, getParameterByName, generateUUID, logError, logInfo, isNumber, pick, debugTurnedOn } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { config } from '../src/config.js'; -import * as utils from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; @@ -8,6 +8,8 @@ import { getHook } from '../src/hook.js'; import { createBid } from '../src/bidfactory.js'; import find from 'core-js-pure/features/array/find.js'; import { getRefererInfo } from '../src/refererDetection.js'; +import {auctionManager} from '../src/auctionManager.js'; +import {bidderSettings} from '../src/bidderSettings.js'; /** * @summary This Module is intended to provide users with the ability to dynamically set and enforce price floors on a per auction basis. @@ -60,19 +62,30 @@ function roundUp(number, precision) { let referrerHostname; function getHostNameFromReferer(referer) { - referrerHostname = utils.parseUrl(referer, {noDecodeWholeURL: true}).hostname; + referrerHostname = parseUrl(referer, {noDecodeWholeURL: true}).hostname; return referrerHostname; } +// First look into bidRequest! +function getGptSlotFromAdUnit(transactionId, {index = auctionManager.index} = {}) { + const adUnit = index.getAdUnit({transactionId}); + const isGam = deepAccess(adUnit, 'ortb2Imp.ext.data.adserver.name') === 'gam'; + return isGam && adUnit.ortb2Imp.ext.data.adserver.adslot; +} + +function getAdUnitCode(request, response, {index = auctionManager.index} = {}) { + return request?.adUnitCode || index.getAdUnit(response).code; +} + /** * @summary floor field types with their matching functions to resolve the actual matched value */ export let fieldMatchingFunctions = { - 'size': (bidRequest, bidResponse) => utils.parseGPTSingleSizeArray(bidResponse.size) || '*', + 'size': (bidRequest, bidResponse) => parseGPTSingleSizeArray(bidResponse.size) || '*', 'mediaType': (bidRequest, bidResponse) => bidResponse.mediaType || 'banner', - 'gptSlot': (bidRequest, bidResponse) => utils.getGptSlotInfoForAdUnitCode(bidRequest.adUnitCode).gptSlot, + 'gptSlot': (bidRequest, bidResponse) => getGptSlotFromAdUnit((bidRequest || bidResponse).transactionId) || getGptSlotInfoForAdUnitCode(getAdUnitCode(bidRequest, bidResponse)).gptSlot, 'domain': (bidRequest, bidResponse) => referrerHostname || getHostNameFromReferer(getRefererInfo().referer), - 'adUnitCode': (bidRequest, bidResponse) => bidRequest.adUnitCode + 'adUnitCode': (bidRequest, bidResponse) => getAdUnitCode(bidRequest, bidResponse) } /** @@ -95,17 +108,17 @@ function enumeratePossibleFieldValues(floorFields, bidObject, responseObject) { * Generates all possible rule matches and picks the first matching one. */ export function getFirstMatchingFloor(floorData, bidObject, responseObject = {}) { - let fieldValues = enumeratePossibleFieldValues(utils.deepAccess(floorData, 'schema.fields') || [], bidObject, responseObject); + let fieldValues = enumeratePossibleFieldValues(deepAccess(floorData, 'schema.fields') || [], bidObject, responseObject); if (!fieldValues.length) return { matchingFloor: floorData.default }; // look to see if a request for this context was made already let matchingInput = fieldValues.map(field => field[0]).join('-'); // if we already have gotten the matching rule from this matching input then use it! No need to look again - let previousMatch = utils.deepAccess(floorData, `matchingInputs.${matchingInput}`); + let previousMatch = deepAccess(floorData, `matchingInputs.${matchingInput}`); if (previousMatch) { return {...previousMatch}; } - let allPossibleMatches = generatePossibleEnumerations(fieldValues, utils.deepAccess(floorData, 'schema.delimiter') || '|'); + let allPossibleMatches = generatePossibleEnumerations(fieldValues, deepAccess(floorData, 'schema.delimiter') || '|'); let matchingRule = find(allPossibleMatches, hashValue => floorData.values.hasOwnProperty(hashValue)); let matchingData = { @@ -116,7 +129,7 @@ export function getFirstMatchingFloor(floorData, bidObject, responseObject = {}) }; matchingData.matchingFloor = Math.max(matchingData.floorMin, matchingData.floorRuleValue); // save for later lookup if needed - utils.deepSetValue(floorData, `matchingInputs.${matchingInput}`, {...matchingData}); + deepSetValue(floorData, `matchingInputs.${matchingInput}`, {...matchingData}); return matchingData; } @@ -141,7 +154,7 @@ function generatePossibleEnumerations(arrayOfFields, delimiter) { * @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted */ export function getBiddersCpmAdjustment(bidderName, inputCpm, bid = {}) { - const adjustmentFunction = utils.deepAccess(getGlobal(), `bidderSettings.${bidderName}.bidCpmAdjustment`) || utils.deepAccess(getGlobal(), 'bidderSettings.standard.bidCpmAdjustment'); + const adjustmentFunction = bidderSettings.get(bidderName, 'bidCpmAdjustment'); if (adjustmentFunction) { return parseFloat(adjustmentFunction(inputCpm, {...bid, cpm: inputCpm})); } @@ -161,9 +174,9 @@ export function calculateAdjustedFloor(oldFloor, newFloor) { * @summary gets the prebid set sizes depending on the input mediaType */ const getMediaTypesSizes = { - banner: (bid) => utils.deepAccess(bid, 'mediaTypes.banner.sizes') || [], - video: (bid) => utils.deepAccess(bid, 'mediaTypes.video.playerSize') || [], - native: (bid) => utils.deepAccess(bid, 'mediaTypes.native.image.sizes') ? [utils.deepAccess(bid, 'mediaTypes.native.image.sizes')] : [] + banner: (bid) => deepAccess(bid, 'mediaTypes.banner.sizes') || [], + video: (bid) => deepAccess(bid, 'mediaTypes.video.playerSize') || [], + native: (bid) => deepAccess(bid, 'mediaTypes.native.image.sizes') ? [deepAccess(bid, 'mediaTypes.native.image.sizes')] : [] } /** @@ -202,7 +215,7 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: try { floorInfo.matchingFloor = getGlobal().convertCurrency(floorInfo.matchingFloor, floorData.data.currency, currency); } catch (err) { - utils.logWarn(`${MODULE_NAME}: Unable to get currency conversion for getFloor for bidder ${bidRequest.bidder}. You must have currency module enabled with defaultRates in your currency config`); + logWarn(`${MODULE_NAME}: Unable to get currency conversion for getFloor for bidder ${bidRequest.bidder}. You must have currency module enabled with defaultRates in your currency config`); // since we were unable to convert to the bidders requested currency, we send back just the actual floors currency to them currency = floorData.data.currency; } @@ -227,7 +240,7 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: * @summary Takes a floorsData object and converts it into a hash map with appropriate keys */ export function getFloorsDataForAuction(floorData, adUnitCode) { - let auctionFloorData = utils.deepClone(floorData); + let auctionFloorData = deepClone(floorData); auctionFloorData.schema.delimiter = floorData.schema.delimiter || '|'; auctionFloorData.values = normalizeRulesForAuction(auctionFloorData, adUnitCode); // default the currency to USD if not passed in @@ -290,10 +303,10 @@ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { skipped: floorData.skipped, skipRate: floorData.skipRate, floorMin: floorData.floorMin, - modelVersion: utils.deepAccess(floorData, 'data.modelVersion'), - modelWeight: utils.deepAccess(floorData, 'data.modelWeight'), - modelTimestamp: utils.deepAccess(floorData, 'data.modelTimestamp'), - location: utils.deepAccess(floorData, 'data.location', 'noData'), + modelVersion: deepAccess(floorData, 'data.modelVersion'), + modelWeight: deepAccess(floorData, 'data.modelWeight'), + modelTimestamp: deepAccess(floorData, 'data.modelTimestamp'), + location: deepAccess(floorData, 'data.location', 'noData'), floorProvider: floorData.floorProvider, fetchStatus: _floorsConfig.fetchStatus }; @@ -317,27 +330,27 @@ export function pickRandomModel(modelGroups, weightSum) { * @summary Updates the adUnits accordingly and returns the necessary floorsData for the current auction */ export function createFloorsDataForAuction(adUnits, auctionId) { - let resolvedFloorsData = utils.deepClone(_floorsConfig); + let resolvedFloorsData = deepClone(_floorsConfig); // if using schema 2 pick a model here: - if (utils.deepAccess(resolvedFloorsData, 'data.floorsSchemaVersion') === 2) { + if (deepAccess(resolvedFloorsData, 'data.floorsSchemaVersion') === 2) { // merge the models specific stuff into the top level data settings (now it looks like floorsSchemaVersion 1!) let { modelGroups, ...rest } = resolvedFloorsData.data; resolvedFloorsData.data = Object.assign(rest, pickRandomModel(modelGroups, rest.modelWeightSum)); } // if we do not have a floors data set, we will try to use data set on adUnits - let useAdUnitData = Object.keys(utils.deepAccess(resolvedFloorsData, 'data.values') || {}).length === 0; + let useAdUnitData = Object.keys(deepAccess(resolvedFloorsData, 'data.values') || {}).length === 0; if (useAdUnitData) { resolvedFloorsData.data = getFloorDataFromAdUnits(adUnits); } else { resolvedFloorsData.data = getFloorsDataForAuction(resolvedFloorsData.data); } // if we still do not have a valid floor data then floors is not on for this auction, so skip - if (Object.keys(utils.deepAccess(resolvedFloorsData, 'data.values') || {}).length === 0) { + if (Object.keys(deepAccess(resolvedFloorsData, 'data.values') || {}).length === 0) { resolvedFloorsData.skipped = true; } else { // determine the skip rate now - const auctionSkipRate = utils.getParameterByName('pbjs_skipRate') || resolvedFloorsData.skipRate; + const auctionSkipRate = getParameterByName('pbjs_skipRate') || resolvedFloorsData.skipRate; const isSkipped = Math.random() * 100 < parseFloat(auctionSkipRate); resolvedFloorsData.skipped = isSkipped; } @@ -358,7 +371,7 @@ export function continueAuction(hookConfig) { _delayedAuctions = _delayedAuctions.filter(auctionConfig => auctionConfig.timer !== hookConfig.timer); // We need to know the auctionId at this time. So we will use the passed in one or generate and set it ourselves - hookConfig.reqBidsConfigObj.auctionId = hookConfig.reqBidsConfigObj.auctionId || utils.generateUUID(); + hookConfig.reqBidsConfigObj.auctionId = hookConfig.reqBidsConfigObj.auctionId || generateUUID(); // now we do what we need to with adUnits and save the data object to be used for getFloor and enforcement calls _floorDataForAuction[hookConfig.reqBidsConfigObj.auctionId] = createFloorsDataForAuction(hookConfig.reqBidsConfigObj.adUnits || getGlobal().adUnits, hookConfig.reqBidsConfigObj.auctionId); @@ -372,7 +385,7 @@ function validateSchemaFields(fields) { if (Array.isArray(fields) && fields.length > 0 && fields.every(field => allowedFields.indexOf(field) !== -1)) { return true; } - utils.logError(`${MODULE_NAME}: Fields recieved do not match allowed fields`); + logError(`${MODULE_NAME}: Fields recieved do not match allowed fields`); return false; } @@ -400,7 +413,7 @@ function validateRules(floorsData, numFields, delimiter) { function modelIsValid(model) { // schema.fields has only allowed attributes - if (!validateSchemaFields(utils.deepAccess(model, 'schema.fields'))) { + if (!validateSchemaFields(deepAccess(model, 'schema.fields'))) { return false; } return validateRules(model, model.schema.fields.length, model.schema.delimiter || '|') @@ -440,7 +453,7 @@ export function isFloorsDataValid(floorsData) { } floorsData.floorsSchemaVersion = floorsData.floorsSchemaVersion || 1; if (typeof floorsSchemaValidation[floorsData.floorsSchemaVersion] !== 'function') { - utils.logError(`${MODULE_NAME}: Unknown floorsSchemaVersion: `, floorsData.floorsSchemaVersion); + logError(`${MODULE_NAME}: Unknown floorsSchemaVersion: `, floorsData.floorsSchemaVersion); return false; } return floorsSchemaValidation[floorsData.floorsSchemaVersion](floorsData); @@ -451,13 +464,13 @@ export function isFloorsDataValid(floorsData) { */ export function parseFloorData(floorsData, location) { if (floorsData && typeof floorsData === 'object' && isFloorsDataValid(floorsData)) { - utils.logInfo(`${MODULE_NAME}: A ${location} set the auction floor data set to `, floorsData); + logInfo(`${MODULE_NAME}: A ${location} set the auction floor data set to `, floorsData); return { ...floorsData, location }; } - utils.logError(`${MODULE_NAME}: The floors data did not contain correct values`, floorsData); + logError(`${MODULE_NAME}: The floors data did not contain correct values`, floorsData); } /** @@ -478,7 +491,7 @@ export function requestBidsHook(fn, reqBidsConfigObj) { // If auction delay > 0 AND we are fetching -> Then wait until it finishes if (_floorsConfig.auctionDelay > 0 && fetching) { hookConfig.timer = setTimeout(() => { - utils.logWarn(`${MODULE_NAME}: Fetch attempt did not return in time for auction`); + logWarn(`${MODULE_NAME}: Fetch attempt did not return in time for auction`); _floorsConfig.fetchStatus = 'timeout'; continueAuction(hookConfig); }, _floorsConfig.auctionDelay); @@ -520,7 +533,7 @@ export function handleFetchResponse(fetchResponse) { // set .data to it _floorsConfig.data = fetchData; // set skipRate override if necessary - _floorsConfig.skipRate = utils.isNumber(fetchData.skipRate) ? fetchData.skipRate : _floorsConfig.skipRate; + _floorsConfig.skipRate = isNumber(fetchData.skipRate) ? fetchData.skipRate : _floorsConfig.skipRate; _floorsConfig.floorProvider = fetchData.floorProvider || _floorsConfig.floorProvider; } @@ -531,7 +544,7 @@ export function handleFetchResponse(fetchResponse) { function handleFetchError(status) { fetching = false; _floorsConfig.fetchStatus = 'error'; - utils.logError(`${MODULE_NAME}: Fetch errored with: `, status); + logError(`${MODULE_NAME}: Fetch errored with: `, status); // if any auctions are waiting for fetch to finish, we need to continue them! resumeDelayedAuctions(); @@ -547,13 +560,13 @@ export function generateAndHandleFetch(floorEndpoint) { // default to GET and we only support GET for now let requestMethod = floorEndpoint.method || 'GET'; if (requestMethod !== 'GET') { - utils.logError(`${MODULE_NAME}: 'GET' is the only request method supported at this time!`); + logError(`${MODULE_NAME}: 'GET' is the only request method supported at this time!`); } else { ajax(floorEndpoint.url, { success: handleFetchResponse, error: handleFetchError }, null, { method: 'GET' }); fetching = true; } } else if (fetching) { - utils.logWarn(`${MODULE_NAME}: A fetch is already occuring. Skipping.`); + logWarn(`${MODULE_NAME}: A fetch is already occuring. Skipping.`); } } @@ -574,14 +587,14 @@ function addFieldOverrides(overrides) { * @summary This is the function which controls what happens during a pbjs.setConfig({...floors: {}}) is called */ export function handleSetFloorsConfig(config) { - _floorsConfig = utils.pick(config, [ + _floorsConfig = pick(config, [ 'floorMin', 'enabled', enabled => enabled !== false, // defaults to true 'auctionDelay', auctionDelay => auctionDelay || 0, - 'floorProvider', floorProvider => utils.deepAccess(config, 'data.floorProvider', floorProvider), + 'floorProvider', floorProvider => deepAccess(config, 'data.floorProvider', floorProvider), 'endpoint', endpoint => endpoint || {}, - 'skipRate', () => !isNaN(utils.deepAccess(config, 'data.skipRate')) ? config.data.skipRate : config.skipRate || 0, - 'enforcement', enforcement => utils.pick(enforcement || {}, [ + 'skipRate', () => !isNaN(deepAccess(config, 'data.skipRate')) ? config.data.skipRate : config.skipRate || 0, + 'enforcement', enforcement => pick(enforcement || {}, [ 'enforceJS', enforceJS => enforceJS !== false, // defaults to true 'enforcePBS', enforcePBS => enforcePBS === true, // defaults to false 'floorDeals', floorDeals => floorDeals === true, // defaults to false @@ -607,11 +620,11 @@ export function handleSetFloorsConfig(config) { getGlobal().requestBids.before(requestBidsHook, 50); // if user has debug on then we want to allow the debugging module to run before this, assuming they are testing priceFloors // debugging is currently set at 5 priority - getHook('addBidResponse').before(addBidResponseHook, utils.debugTurnedOn() ? 4 : 50); + getHook('addBidResponse').before(addBidResponseHook, debugTurnedOn() ? 4 : 50); addedFloorsHook = true; } } else { - utils.logInfo(`${MODULE_NAME}: Turning off module`); + logInfo(`${MODULE_NAME}: Turning off module`); _floorsConfig = {}; _floorDataForAuction = {}; @@ -647,8 +660,8 @@ function addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm) { * @summary takes the enforcement flags and the bid itself and determines if it should be floored */ function shouldFloorBid(floorData, floorInfo, bid) { - let enforceJS = utils.deepAccess(floorData, 'enforcement.enforceJS') !== false; - let shouldFloorDeal = utils.deepAccess(floorData, 'enforcement.floorDeals') === true || !bid.dealId; + let enforceJS = deepAccess(floorData, 'enforcement.enforceJS') !== false; + let shouldFloorDeal = deepAccess(floorData, 'enforcement.floorDeals') === true || !bid.dealId; let bidBelowFloor = bid.floorData.cpmAfterAdjustments < floorInfo.matchingFloor; return enforceJS && (bidBelowFloor && shouldFloorDeal); } @@ -658,18 +671,19 @@ function shouldFloorBid(floorData, floorInfo, bid) { * And if the rule we find determines a bid should be floored we will do so. */ export function addBidResponseHook(fn, adUnitCode, bid) { - let floorData = _floorDataForAuction[this.bidderRequest.auctionId]; - // if no floor data or associated bidRequest then bail - const matchingBidRequest = find(this.bidderRequest.bids, bidRequest => bidRequest.bidId && bidRequest.bidId === bid.requestId); - if (!floorData || !bid || floorData.skipped || !matchingBidRequest) { + let floorData = _floorDataForAuction[bid.auctionId]; + // if no floor data then bail + if (!floorData || !bid || floorData.skipped) { return fn.call(this, adUnitCode, bid); } + const matchingBidRequest = auctionManager.index.getBidRequest(bid) + // get the matching rule - let floorInfo = getFirstMatchingFloor(floorData.data, {...matchingBidRequest}, {...bid, size: [bid.width, bid.height]}); + let floorInfo = getFirstMatchingFloor(floorData.data, matchingBidRequest, {...bid, size: [bid.width, bid.height]}); if (!floorInfo.matchingFloor) { - utils.logWarn(`${MODULE_NAME}: unable to determine a matching price floor for bidResponse`, bid); + logWarn(`${MODULE_NAME}: unable to determine a matching price floor for bidResponse`, bid); return fn.call(this, adUnitCode, bid); } @@ -685,7 +699,7 @@ export function addBidResponseHook(fn, adUnitCode, bid) { try { adjustedCpm = getGlobal().convertCurrency(bid.cpm, bidResponseCurrency.toUpperCase(), floorCurrency); } catch (err) { - utils.logError(`${MODULE_NAME}: Unable do get currency conversion for bidResponse to Floor Currency. Do you have Currency module enabled? ${bid}`); + logError(`${MODULE_NAME}: Unable do get currency conversion for bidResponse to Floor Currency. Do you have Currency module enabled? ${bid}`); return fn.call(this, adUnitCode, bid); } } @@ -700,8 +714,8 @@ export function addBidResponseHook(fn, adUnitCode, bid) { if (shouldFloorBid(floorData, floorInfo, bid)) { // bid fails floor -> throw it out // create basic bid no-bid with necessary data fro analytics adapters - let flooredBid = createBid(CONSTANTS.STATUS.NO_BID, matchingBidRequest); - Object.assign(flooredBid, utils.pick(bid, [ + let flooredBid = createBid(CONSTANTS.STATUS.NO_BID, bid.getIdentifiers()); + Object.assign(flooredBid, pick(bid, [ 'floorData', 'width', 'height', @@ -714,7 +728,7 @@ export function addBidResponseHook(fn, adUnitCode, bid) { flooredBid.status = CONSTANTS.BID_STATUS.BID_REJECTED; // if floor not met update bid with 0 cpm so it is not included downstream and marked as no-bid flooredBid.cpm = 0; - utils.logWarn(`${MODULE_NAME}: ${flooredBid.bidderCode}'s Bid Response for ${adUnitCode} was rejected due to floor not met`, bid); + logWarn(`${MODULE_NAME}: ${flooredBid.bidderCode}'s Bid Response for ${adUnitCode} was rejected due to floor not met`, bid); return fn.call(this, adUnitCode, flooredBid); } return fn.call(this, adUnitCode, bid); diff --git a/modules/projectLimeLightBidAdapter.js b/modules/projectLimeLightBidAdapter.js deleted file mode 100644 index 1beba906917..00000000000 --- a/modules/projectLimeLightBidAdapter.js +++ /dev/null @@ -1,122 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import {ajax} from '../src/ajax.js'; -import * as utils from '../src/utils.js'; - -const BIDDER_CODE = 'project-limelight'; - -/** - * Determines whether or not the given bid response is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastXml || bid.vastUrl); - } - return false; -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], - - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: (validBidRequests, bidderRequest) => { - let winTop; - try { - winTop = window.top; - winTop.location.toString(); - } catch (e) { - utils.logMessage(e); - winTop = window; - } - const placements = utils.groupBy(validBidRequests.map(bidRequest => buildPlacement(bidRequest)), 'host') - return Object.keys(placements) - .map(host => buildRequest(winTop, host, placements[host].map(placement => placement.adUnit))); - }, - - onBidWon: (bid) => { - const cpm = bid.pbMg; - if (bid.nurl !== '') { - bid.nurl = bid.nurl.replace( - /\$\{AUCTION_PRICE\}/, - cpm - ); - ajax(bid.nurl, null); - }; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: (bidResponses) => { - const res = []; - const bidResponsesBody = bidResponses.body; - const len = bidResponsesBody.length; - for (let i = 0; i < len; i++) { - const bid = bidResponsesBody[i]; - if (isBidResponseValid(bid)) { - res.push(bid); - } - } - return res; - }, -}; - -registerBidder(spec); - -function buildRequest(winTop, host, adUnits) { - return { - method: 'POST', - url: `https://${host}/hb`, - data: { - secure: (location.protocol === 'https:'), - deviceWidth: winTop.screen.width, - deviceHeight: winTop.screen.height, - adUnits: adUnits - } - } -} - -function buildPlacement(bidRequest) { - return { - host: bidRequest.params.host, - adUnit: { - id: bidRequest.params.adUnitId, - bidId: bidRequest.bidId, - transactionId: bidRequest.transactionId, - sizes: bidRequest.sizes.map(size => { - return { - width: size[0], - height: size[1] - } - }), - type: bidRequest.params.adUnitType.toUpperCase() - } - } -} diff --git a/modules/projectLimeLightBidAdapter.md b/modules/projectLimeLightBidAdapter.md deleted file mode 100644 index 15aa170cd2e..00000000000 --- a/modules/projectLimeLightBidAdapter.md +++ /dev/null @@ -1,57 +0,0 @@ -# Overview - -``` -Module Name: Project LimeLight SSP Adapter -Module Type: Bidder Adapter -Maintainer: engineering@project-limelight.com -``` - -# Description - -Module that connects to Project Limelight SSP demand sources - -# Test Parameters for banner -``` - var adUnits = [{ - code: 'placementCode', - sizes: [[300, 250]], - bids: [{ - bidder: 'project-limelight', - params: { - host: 'ads.project-limelight.com', - adUnitId: 0, - adUnitType: 'banner' - } - }] - } - ]; -``` - -# Test Parameters for video -``` -var videoAdUnit = [{ - code: 'video1', - sizes: [[300, 250]], - bids: [{ - bidder: 'project-limelight', - params: { - host: 'ads.project-limelight.com', - adUnitId: 0, - adUnitType: 'video' - } - }] - }]; -``` - -# Configuration - -The Project Limelight Bid Adapter expects Prebid Cache(for video) to be enabled so that we can store and retrieve a single vastXml. - -``` -pbjs.setConfig({ - usePrebidCache: true, - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache' - } -}); -``` diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index ff07ee9ede9..42a98bcdb09 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -1,6 +1,11 @@ +import { isFn, isPlainObject } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; + const BIDDER_CODE = 'proxistore'; const PROXISTORE_VENDOR_ID = 418; +const COOKIE_BASE_URL = 'https://abs.proxistore.com/v3/rtb/prebid/multi'; +const COOKIE_LESS_URL = + 'https://abs.cookieless-proxistore.com/v3/rtb/prebid/multi'; function _createServerRequest(bidRequests, bidderRequest) { var sizeIds = []; @@ -26,38 +31,63 @@ function _createServerRequest(bidRequests, bidderRequest) { language: bidRequests[0].params.language, gdpr: { applies: false, - consentGiven: false + consentGiven: false, }, }; if (bidderRequest && bidderRequest.gdprConsent) { - const { gdprConsent } = bidderRequest; - if (typeof gdprConsent.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { + var gdprConsent = bidderRequest.gdprConsent; + + if ( + typeof gdprConsent.gdprApplies === 'boolean' && + gdprConsent.gdprApplies + ) { payload.gdpr.applies = true; } - if (typeof gdprConsent.consentString === 'string' && gdprConsent.consentString) { + if ( + typeof gdprConsent.consentString === 'string' && + gdprConsent.consentString + ) { payload.gdpr.consentString = bidderRequest.gdprConsent.consentString; } + if (gdprConsent.vendorData) { - const {vendorData} = gdprConsent; - const {apiVersion} = gdprConsent; - if (apiVersion === 2 && vendorData.vendor && vendorData.vendor.consents && typeof vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)] !== 'undefined') { - payload.gdpr.consentGiven = !!vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)]; - } else if (apiVersion === 1 && vendorData.vendorConsents && typeof vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)] !== 'undefined') { - payload.gdpr.consentGiven = !!vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)]; + var vendorData = gdprConsent.vendorData; + var apiVersion = gdprConsent.apiVersion; + + if ( + apiVersion === 2 && + vendorData.vendor && + vendorData.vendor.consents && + typeof vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)] !== + 'undefined' + ) { + payload.gdpr.consentGiven = + !!vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)]; + } else if ( + apiVersion === 1 && + vendorData.vendorConsents && + typeof vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)] !== + 'undefined' + ) { + payload.gdpr.consentGiven = + !!vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)]; } } } - const options = { + var options = { contentType: 'application/json', withCredentials: payload.gdpr.consentGiven, + customHeaders: { + version: '1.0.4', + }, }; - - const endPointUri = payload.gdpr.consentGiven || !payload.gdpr.applies - ? `https://abs.proxistore.com/${payload.language}/v3/rtb/prebid/multi` - : `https://abs.cookieless-proxistore.com/${payload.language}/v3/rtb/prebid/multi`; + var endPointUri = + payload.gdpr.consentGiven || !payload.gdpr.applies + ? COOKIE_BASE_URL + : COOKIE_LESS_URL; return { method: 'POST', @@ -68,10 +98,24 @@ function _createServerRequest(bidRequests, bidderRequest) { } function _assignSegments(bid) { - if (bid.ortb2 && bid.ortb2.user && bid.ortb2.user.ext && bid.ortb2.user.ext.data) { - return bid.ortb2.user.ext.data || {segments: [], contextual_categories: {}}; + if ( + bid.ortb2 && + bid.ortb2.user && + bid.ortb2.user.ext && + bid.ortb2.user.ext.data + ) { + return ( + bid.ortb2.user.ext.data || { + segments: [], + contextual_categories: {}, + } + ); } - return {segments: [], contextual_categories: {}}; + + return { + segments: [], + contextual_categories: {}, + }; } function _createBidResponse(response) { @@ -88,6 +132,7 @@ function _createBidResponse(response) { vastUrl: response.vastUrl, vastXml: response.vastXml, dealId: response.dealId, + meta: response.meta, }; } /** @@ -110,6 +155,7 @@ function isBidRequestValid(bid) { function buildRequests(bidRequests, bidderRequest) { var request = _createServerRequest(bidRequests, bidderRequest); + return request; } /** @@ -125,18 +171,18 @@ function interpretResponse(serverResponse, bidRequest) { } function _assignFloor(bid) { - if (typeof bid.getFloor === 'function') { - var floorInfo = bid.getFloor({ - currency: 'EUR', - mediaType: 'banner', - size: '*', - }); - - if (floorInfo.currency === 'EUR') { - return floorInfo.floor; - } + if (!isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; } + const floor = bid.getFloor({ + currency: 'EUR', + mediaType: 'banner', + size: '*', + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'EUR') { + return floor.floor; + } return null; } @@ -145,7 +191,7 @@ export const spec = { isBidRequestValid: isBidRequestValid, buildRequests: buildRequests, interpretResponse: interpretResponse, - + gvlid: PROXISTORE_VENDOR_ID, }; registerBidder(spec); diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index 427f775c44b..6ecf0723aae 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -3,7 +3,7 @@ * stored in the page's domain. When the module is included, an id is generated if needed, * persisted as a cookie, and automatically appended to all the bidRequest as bid.crumbs.pubcid. */ -import * as utils from '../src/utils.js'; +import { logMessage, parseUrl, buildUrl, triggerPixel, generateUUID, isArray } from '../src/utils.js'; import { config } from '../src/config.js'; import events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; @@ -44,7 +44,7 @@ export function setStorageItem(key, val, expires) { storage.setDataInLocalStorage(key, val); } catch (e) { - utils.logMessage(e); + logMessage(e); } } @@ -74,7 +74,7 @@ export function getStorageItem(key) { } } } catch (e) { - utils.logMessage(e); + logMessage(e); } return val; @@ -89,7 +89,7 @@ export function removeStorageItem(key) { storage.removeDataFromLocalStorage(key + EXP_SUFFIX); storage.removeDataFromLocalStorage(key); } catch (e) { - utils.logMessage(e); + logMessage(e); } } @@ -141,13 +141,13 @@ function queuePixelCallback(pixelUrl, id) { id = id || ''; // Use pubcid as a cache buster - const urlInfo = utils.parseUrl(pixelUrl); + const urlInfo = parseUrl(pixelUrl); urlInfo.search.id = encodeURIComponent('pubcid:' + id); - const targetUrl = utils.buildUrl(urlInfo); + const targetUrl = buildUrl(urlInfo); events.on(CONSTANTS.EVENTS.AUCTION_END, function auctionEndHandler() { events.off(CONSTANTS.EVENTS.AUCTION_END, auctionEndHandler); - utils.triggerPixel(targetUrl); + triggerPixel(targetUrl); }); return true; @@ -177,7 +177,7 @@ export function requestBidHook(next, config) { if (typeof window[PUB_COMMON] === 'object') { // If the page includes its own pubcid object, then use that instead. pubcid = window[PUB_COMMON].getId(); - utils.logMessage(PUB_COMMON + ': pubcid = ' + pubcid); + logMessage(PUB_COMMON + ': pubcid = ' + pubcid); } else { // Otherwise get the existing cookie pubcid = readValue(ID_NAME); @@ -190,7 +190,7 @@ export function requestBidHook(next, config) { } // Generate a new id if (!pubcid) { - pubcid = utils.generateUUID(); + pubcid = generateUUID(); } // Update the cookie/storage with the latest expiration date writeValue(ID_NAME, pubcid, pubcidConfig.interval); @@ -205,14 +205,14 @@ export function requestBidHook(next, config) { } } - utils.logMessage('pbjs: pubcid = ' + pubcid); + logMessage('pbjs: pubcid = ' + pubcid); } // Append pubcid to each bid object, which will be incorporated // into bid requests later. if (adUnits && pubcid) { adUnits.forEach((unit) => { - if (unit.bids && utils.isArray(unit.bids)) { + if (unit.bids && isArray(unit.bids)) { unit.bids.forEach((bid) => { Object.assign(bid, {crumbs: {pubcid}}); }); diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js deleted file mode 100644 index 95e539a4d6a..00000000000 --- a/modules/pubCommonIdSystem.js +++ /dev/null @@ -1,345 +0,0 @@ -/** - * This module adds PubCommonId to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/pubCommonIdSystem - * @requires module:modules/userId - */ - -import * as utils from '../src/utils.js'; -import {submodule} from '../src/hook.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {ajax} from '../src/ajax.js'; -import { uspDataHandler, coppaDataHandler } from '../src/adapterManager.js'; - -const PUB_COMMON_ID = 'PublisherCommonId'; -const MODULE_NAME = 'pubCommonId'; - -const COOKIE = 'cookie'; -const LOCAL_STORAGE = 'html5'; -const SHAREDID_OPT_OUT_VALUE = '00000000000000000000000000'; -const SHAREDID_URL = 'https://id.sharedid.org/id'; -const SHAREDID_SUFFIX = '_sharedid'; -const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; -const SHAREDID_DEFAULT_STATE = false; -const GVLID = 887; - -const storage = getStorageManager(GVLID, 'pubCommonId'); - -/** - * Store sharedid in either cookie or local storage - * @param {Object} config Need config.storage object to derive key, expiry time, and storage type. - * @param {string} value Shareid value to store - */ - -function storeData(config, value) { - try { - if (value) { - const key = config.storage.name + SHAREDID_SUFFIX; - const expiresStr = (new Date(Date.now() + (storage.expires * (60 * 60 * 24 * 1000)))).toUTCString(); - - if (config.storage.type === COOKIE) { - if (storage.cookiesAreEnabled()) { - storage.setCookie(key, value, expiresStr, 'LAX', pubCommonIdSubmodule.domainOverride()); - } - } else if (config.storage.type === LOCAL_STORAGE) { - if (storage.hasLocalStorage()) { - storage.setDataInLocalStorage(`${key}_exp`, expiresStr); - storage.setDataInLocalStorage(key, value); - } - } - } - } catch (error) { - utils.logError(error); - } -} - -/** - * Read sharedid from cookie or local storage - * @param config Need config.storage to derive key and storage type - * @return {string} - */ -function readData(config) { - try { - const key = config.storage.name + SHAREDID_SUFFIX; - if (config.storage.type === COOKIE) { - if (storage.cookiesAreEnabled()) { - return storage.getCookie(key); - } - } else if (config.storage.type === LOCAL_STORAGE) { - if (storage.hasLocalStorage()) { - const expValue = storage.getDataFromLocalStorage(`${key}_exp`); - if (!expValue) { - return storage.getDataFromLocalStorage(key); - } else if ((new Date(expValue)).getTime() - Date.now() > 0) { - return storage.getDataFromLocalStorage(key) - } - } - } - } catch (error) { - utils.logError(error); - } -} - -/** - * Delete sharedid from cookie or local storage - * @param config Need config.storage to derive key and storage type - */ -function delData(config) { - try { - const key = config.storage.name + SHAREDID_SUFFIX; - if (config.storage.type === COOKIE) { - if (storage.cookiesAreEnabled()) { - storage.setCookie(key, '', EXPIRED_COOKIE_DATE); - } - } else if (config.storage.type === LOCAL_STORAGE) { - storage.removeDataFromLocalStorage(`${key}_exp`); - storage.removeDataFromLocalStorage(key); - } - } catch (error) { - utils.logError(error); - } -} - -/** - * setup success and error handler for sharedid callback thru ajax - * @param {string} pubcid Current pubcommon id - * @param {function} callback userId module callback. - * @param {Object} config Need config.storage to derive sharedid storage params - * @return {{success: success, error: error}} - */ - -function handleResponse(pubcid, callback, config) { - return { - success: function (responseBody) { - if (responseBody) { - try { - let responseObj = JSON.parse(responseBody); - utils.logInfo('PubCommonId: Generated SharedId: ' + responseObj.sharedId); - if (responseObj.sharedId) { - if (responseObj.sharedId !== SHAREDID_OPT_OUT_VALUE) { - // Store sharedId locally - storeData(config, responseObj.sharedId); - } else { - // Delete local copy if the user has opted out - delData(config); - } - } - // Pass pubcid even though there is no change in order to trigger decode - callback(pubcid); - } catch (error) { - utils.logError(error); - } - } - }, - error: function (statusText, responseBody) { - utils.logInfo('PubCommonId: failed to get sharedid'); - } - } -} - -/** - * Builds and returns the shared Id URL with attached consent data if applicable - * @param {Object} consentData - * @return {string} - */ -function sharedIdUrl(consentData) { - const usPrivacyString = uspDataHandler.getConsentData(); - let sharedIdUrl = SHAREDID_URL; - if (usPrivacyString && typeof usPrivacyString === 'string') { - sharedIdUrl = `${SHAREDID_URL}?us_privacy=${usPrivacyString}`; - } - if (!consentData || typeof consentData.gdprApplies !== 'boolean' || !consentData.gdprApplies) return sharedIdUrl; - if (usPrivacyString) { - sharedIdUrl = `${sharedIdUrl}&gdpr=1&gdpr_consent=${consentData.consentString}` - return sharedIdUrl; - } - sharedIdUrl = `${SHAREDID_URL}?gdpr=1&gdpr_consent=${consentData.consentString}`; - return sharedIdUrl -} - -/** - * Wraps pixelCallback in order to call sharedid sync - * @param {string} pubcid Pubcommon id value - * @param {function|undefined} pixelCallback fires a pixel to first party server - * @param {Object} config Need config.storage to derive sharedid storage params. - * @return {function(...[*]=)} - */ - -function getIdCallback(pubcid, pixelCallback, config, consentData) { - return function (callback) { - if (typeof pixelCallback === 'function') { - pixelCallback(); - } - ajax(sharedIdUrl(consentData), handleResponse(pubcid, callback, config), undefined, {method: 'GET', withCredentials: true}); - } -} - -/** @type {Submodule} */ -export const pubCommonIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - /** - * Vendor id of prebid - * @type {Number} - */ - gvlid: GVLID, - /** - * Return a callback function that calls the pixelUrl with id as a query parameter - * @param pixelUrl - * @param id - * @returns {function} - */ - makeCallback: function (pixelUrl, id = '') { - if (!pixelUrl) { - return; - } - - // Use pubcid as a cache buster - const urlInfo = utils.parseUrl(pixelUrl); - urlInfo.search.id = encodeURIComponent('pubcid:' + id); - const targetUrl = utils.buildUrl(urlInfo); - - return function () { - utils.triggerPixel(targetUrl); - }; - }, - /** - * decode the stored id value for passing to bid requests - * @function - * @param {string} value - * @param {SubmoduleConfig} config - * @returns {{pubcid:string}} - */ - decode(value, config) { - const idObj = {'pubcid': value}; - const {params: {enableSharedId = SHAREDID_DEFAULT_STATE} = {}} = config; - - if (enableSharedId) { - const sharedId = readData(config); - if (sharedId) idObj['sharedid'] = {id: sharedId}; - } - - return idObj; - }, - /** - * performs action to obtain id - * @function - * @param {SubmoduleConfig} [config] Config object with params and storage properties - * @param {Object} consentData - * @param {string} storedId Existing pubcommon id - * @returns {IdResponse} - */ - getId: function (config = {}, consentData, storedId) { - const coppa = coppaDataHandler.getCoppa(); - if (coppa) { - utils.logInfo('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); - return; - } - const {params: {create = true, pixelUrl, enableSharedId = SHAREDID_DEFAULT_STATE} = {}} = config; - let newId = storedId; - if (!newId) { - try { - if (typeof window[PUB_COMMON_ID] === 'object') { - // If the page includes its own pubcid module, then save a copy of id. - newId = window[PUB_COMMON_ID].getId(); - } - } catch (e) { - } - - if (!newId) newId = (create && utils.hasDeviceAccess()) ? utils.generateUUID() : undefined; - } - - const pixelCallback = this.makeCallback(pixelUrl, newId); - const combinedCallback = enableSharedId ? getIdCallback(newId, pixelCallback, config, consentData) : pixelCallback; - - return {id: newId, callback: combinedCallback}; - }, - /** - * performs action to extend an id. There are generally two ways to extend the expiration time - * of stored id: using pixelUrl or return the id and let main user id module write it again with - * the new expiration time. - * - * PixelUrl, if defined, should point back to a first party domain endpoint. On the server - * side, there is either a plugin, or customized logic to read and write back the pubcid cookie. - * The extendId function itself should return only the callback, and not the id itself to avoid - * having the script-side overwriting server-side. This applies to both pubcid and sharedid. - * - * On the other hand, if there is no pixelUrl, then the extendId should return storedId so that - * its expiration time is updated. Sharedid, however, will have to be updated by this submodule - * separately. - * - * @function - * @param {SubmoduleParams} [config] - * @param {ConsentData|undefined} consentData - * @param {Object} storedId existing id - * @returns {IdResponse|undefined} - */ - extendId: function(config = {}, consentData, storedId) { - const coppa = coppaDataHandler.getCoppa(); - if (coppa) { - utils.logInfo('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); - return; - } - const {params: {extend = false, pixelUrl, enableSharedId = SHAREDID_DEFAULT_STATE} = {}} = config; - - if (extend) { - try { - if (typeof window[PUB_COMMON_ID] === 'object') { - if (enableSharedId) { - // If the page includes its own pubcid module, then there is nothing to do - // except to update sharedid's expiration time - storeData(config, readData(config)); - } - return; - } - } catch (e) { - } - - if (pixelUrl) { - const callback = this.makeCallback(pixelUrl, storedId); - return {callback: callback}; - } else { - if (enableSharedId) { - // Update with the same value to extend expiration time - storeData(config, readData(config)); - } - return {id: storedId}; - } - } - }, - - /** - * @param {string} domain - * @param {HTMLDocument} document - * @return {(string|undefined)} - */ - domainOverride: function () { - const domainElements = document.domain.split('.'); - const cookieName = `_gd${Date.now()}`; - for (let i = 0, topDomain, testCookie; i < domainElements.length; i++) { - const nextDomain = domainElements.slice(i).join('.'); - - // write test cookie - storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); - - // read test cookie to verify domain was valid - testCookie = storage.getCookie(cookieName); - - // delete test cookie - storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); - - if (testCookie === '1') { - // cookie was written successfully using test domain so the topDomain is updated - topDomain = nextDomain; - } else { - // cookie failed to write using test domain so exit by returning the topDomain - return topDomain; - } - } - } -}; - -submodule('userId', pubCommonIdSubmodule); diff --git a/modules/pubProvidedIdSystem.js b/modules/pubProvidedIdSystem.js index 0b2175f57cb..669d223c57f 100644 --- a/modules/pubProvidedIdSystem.js +++ b/modules/pubProvidedIdSystem.js @@ -6,7 +6,7 @@ */ import {submodule} from '../src/hook.js'; -import * as utils from '../src/utils.js'; +import { logInfo, isArray } from '../src/utils.js'; const MODULE_NAME = 'pubProvidedId'; @@ -27,7 +27,7 @@ export const pubProvidedIdSubmodule = { */ decode(value) { const res = value ? {pubProvidedId: value} : undefined; - utils.logInfo('PubProvidedId: Decoded value ' + JSON.stringify(res)); + logInfo('PubProvidedId: Decoded value ' + JSON.stringify(res)); return res; }, @@ -40,7 +40,7 @@ export const pubProvidedIdSubmodule = { getId(config) { const configParams = (config && config.params) || {}; let res = []; - if (utils.isArray(configParams.eids)) { + if (isArray(configParams.eids)) { res = res.concat(configParams.eids); } if (typeof configParams.eidsFunction === 'function') { diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index 2df2d25f627..28c4fdefd42 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -16,7 +16,7 @@ import { } from '../src/utils.js'; const BIDDER_VERSION = '1.1.0'; -const BASE_URL = 'https://ortb.adpearl.io'; +const BASE_URL = 'https://auction.adpearl.io'; export const spec = { code: 'pubgenius', @@ -149,7 +149,22 @@ export const spec = { function buildVideoParams(videoMediaType, videoParams) { videoMediaType = videoMediaType || {}; - const params = pick(videoMediaType, ['api', 'mimes', 'protocols', 'playbackmethod']); + const params = pick(videoMediaType, [ + 'mimes', + 'minduration', + 'maxduration', + 'protocols', + 'startdelay', + 'placement', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity', + ]); switch (videoMediaType.context) { case 'instream': diff --git a/modules/pubgeniusBidAdapter.md b/modules/pubgeniusBidAdapter.md index 66e0c382285..2a10b421de2 100644 --- a/modules/pubgeniusBidAdapter.md +++ b/modules/pubgeniusBidAdapter.md @@ -65,16 +65,16 @@ var adUnits = [ adUnitId: '1001', test: true, - // other video parameters as in OpenRTB v2.5 spec + // Other video parameters can be put here as in OpenRTB v2.5 spec. + // This overrides the same parameters in mediaTypes.video. video: { - skip: 1 - - // the following overrides mediaTypes.video of the ad unit placement: 1, + + // w and h overrides mediaTypes.video.playerSize. w: 640, h: 360, - mimes: ['video/mp4'], - protocols: [3], + + skip: 1 } } } diff --git a/modules/publinkIdSystem.js b/modules/publinkIdSystem.js new file mode 100644 index 00000000000..990227e7cfe --- /dev/null +++ b/modules/publinkIdSystem.js @@ -0,0 +1,144 @@ +/** + * This module adds the PublinkId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/publinkIdSystem + * @requires module:modules/userId + */ + +import {submodule} from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {ajax} from '../src/ajax.js'; +import { parseUrl, buildUrl, logError } from '../src/utils.js'; +import {uspDataHandler} from '../src/adapterManager.js'; + +const MODULE_NAME = 'publinkId'; +const GVLID = 24; +const PUBLINK_COOKIE = '_publink'; +const PUBLINK_S2S_COOKIE = '_publink_srv'; + +export const storage = getStorageManager(GVLID); + +function isHex(s) { + return /^[A-F0-9]+$/i.test(s); +} + +function publinkIdUrl(params, consentData) { + let url = parseUrl('https://proc.ad.cpe.dotomi.com/cvx/client/sync/publink'); + url.search = { + deh: params.e, + mpn: 'Prebid.js', + mpv: '$prebid.version$', + }; + + if (consentData) { + url.search.gdpr = (consentData.gdprApplies) ? 1 : 0; + url.search.gdpr_consent = consentData.consentString; + } + + if (params.site_id) { url.search.sid = params.site_id; } + + if (params.api_key) { url.search.apikey = params.api_key; } + + const usPrivacyString = uspDataHandler.getConsentData(); + if (usPrivacyString && typeof usPrivacyString === 'string') { + url.search.us_privacy = usPrivacyString; + } + + return buildUrl(url); +} + +function makeCallback(config = {}, consentData) { + return function(prebidCallback) { + const options = {method: 'GET', withCredentials: true}; + let handleResponse = function(responseText, xhr) { + if (xhr.status === 200) { + let response = JSON.parse(responseText); + if (response) { + prebidCallback(response.publink); + } + } + }; + + if (config.params && config.params.e) { + if (isHex(config.params.e)) { + ajax(publinkIdUrl(config.params, consentData), handleResponse, undefined, options); + } else { + logError('params.e must be a hex string'); + } + } + }; +} + +function getlocalValue() { + let result; + function getData(key) { + let value; + if (storage.hasLocalStorage()) { + value = storage.getDataFromLocalStorage(key); + } + if (!value) { + value = storage.getCookie(key); + } + + if (typeof value === 'string') { + // if it's a json object parse it and return the publink value, otherwise assume the value is the id + if (value.charAt(0) === '{') { + try { + const obj = JSON.parse(value); + if (obj) { + return obj.publink; + } + } catch (e) { + logError(e); + } + } else { + return value; + } + } + } + result = getData(PUBLINK_S2S_COOKIE); + if (!result) { + result = getData(PUBLINK_COOKIE); + } + return result; +} + +/** @type {Submodule} */ +export const publinkIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + gvlid: GVLID, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} publinkId encrypted userid + * @returns {{publinkId: string} | undefined} + */ + decode(publinkId) { + return {publinkId: publinkId}; + }, + + /** + * performs action to obtain id + * Use a publink cookie first if it is present, otherwise use prebids copy, if neither are available callout to get a new id + * @function + * @param {SubmoduleConfig} [config] Config object with params and storage properties + * @param {ConsentData|undefined} consentData GDPR consent + * @param {(Object|undefined)} storedId Previously cached id + * @returns {IdResponse} + */ + getId: function(config, consentData, storedId) { + const localValue = getlocalValue(); + if (localValue) { + return {id: localValue}; + } + if (!storedId) { + return {callback: makeCallback(config, consentData)}; + } + } +}; +submodule('userId', publinkIdSubmodule); diff --git a/modules/publinkIdSystem.md b/modules/publinkIdSystem.md new file mode 100644 index 00000000000..263ce490529 --- /dev/null +++ b/modules/publinkIdSystem.md @@ -0,0 +1,33 @@ +## Publink User ID Submodule + +Publink user id module + +## Configuration Descriptions for the `userId` Configuration Section + +| Param Name | Required | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Yes | String | module identifier | `"publinkId"` | +| params.e | Yes | String | hashed email address | `"7D320454942620664D96EF78ED4E3A2A"` | +| params.api_key | Yes | String | api key for access | `"7ab62359-bdc0-4095-b573-ef474fb55d24"` | +| params.site_id | Yes | String | site identifier | `"123456"` | + + +### Example configuration for Publink +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: "publinkId", + storage: { + name: "pbjs_publink", + type: "html5" + }, + params: { + e: "7D320454942620664D96EF78ED4E3A2A", // example hashed email (md5) + site_id: "123456", // provided by Epsilon + api_key: "7ab62359-bdc0-4095-b573-ef474fb55d2" // provided by Epsilon + } + }], + } + }); +``` diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index c7eeaf87fdc..f69fb20e5d5 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -1,9 +1,9 @@ +import { _each, pick, logWarn, isStr, isArray, logError } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; -import * as utils from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; /// /////////// CONSTANTS ////////////// @@ -69,7 +69,7 @@ function setMediaTypes(types, bid) { if (typeof types === 'object') { if (!bid.sizes) { bid.dimensions = []; - utils._each(types, (type) => + _each(types, (type) => bid.dimensions = bid.dimensions.concat( type.sizes.map(sizeToDimensions) ) @@ -81,13 +81,13 @@ function setMediaTypes(types, bid) { } function copyRequiredBidDetails(bid) { - return utils.pick(bid, [ - 'bidder', bidder => bidder.toLowerCase(), + return pick(bid, [ + 'bidder', 'bidId', 'status', () => NO_BID, // default a bid to NO_BID until response is recieved or bid is timed out 'finalSource as source', 'params', - 'adUnit', () => utils.pick(bid, [ + 'adUnit', () => pick(bid, [ 'adUnitCode', 'transactionId', 'sizes as dimensions', sizes => sizes.map(sizeToDimensions), @@ -115,7 +115,7 @@ function setBidStatus(bid, args) { } function parseBidResponse(bid) { - return utils.pick(bid, [ + return pick(bid, [ 'bidPriceUSD', () => { // todo: check whether currency cases are handled here if (typeof bid.currency === 'string' && bid.currency.toUpperCase() === CURRENCY_USD) { @@ -125,7 +125,7 @@ function parseBidResponse(bid) { if (typeof bid.getCpmInNewCurrency === 'function') { return window.parseFloat(Number(bid.getCpmInNewCurrency(CURRENCY_USD)).toFixed(BID_PRECISION)); } - utils.logWarn(LOG_PRE_FIX + 'Could not determine the Net cpm in USD for the bid thus using bid.cpm', bid); + logWarn(LOG_PRE_FIX + 'Could not determine the Net cpm in USD for the bid thus using bid.cpm', bid); return bid.cpm }, 'bidGrossCpmUSD', () => { @@ -136,7 +136,7 @@ function parseBidResponse(bid) { if (typeof getGlobal().convertCurrency === 'function') { return window.parseFloat(Number(getGlobal().convertCurrency(bid.originalCpm, bid.originalCurrency, CURRENCY_USD)).toFixed(BID_PRECISION)); } - utils.logWarn(LOG_PRE_FIX + 'Could not determine the Gross cpm in USD for the bid, thus using bid.originalCpm', bid); + logWarn(LOG_PRE_FIX + 'Could not determine the Gross cpm in USD for the bid, thus using bid.originalCpm', bid); return bid.originalCpm }, 'dealId', @@ -154,7 +154,7 @@ function parseBidResponse(bid) { 'mi', 'regexPattern', () => bid.regexPattern || undefined, 'partnerImpId', // partner impression ID - 'dimensions', () => utils.pick(bid, [ + 'dimensions', () => pick(bid, [ 'width', 'height' ]) @@ -171,7 +171,7 @@ function getDevicePlatform() { var deviceType = 3; try { var ua = navigator.userAgent; - if (ua && utils.isStr(ua) && ua.trim() != '') { + if (ua && isStr(ua) && ua.trim() != '') { ua = ua.toLowerCase().trim(); var isMobileRegExp = new RegExp('(mobi|tablet|ios).*'); if (ua.match(isMobileRegExp)) { @@ -196,12 +196,32 @@ function getValueForKgpv(bid, adUnitId) { } } +function getAdapterNameForAlias(aliasName) { + return adapterManager.aliasRegistry[aliasName] || aliasName; +} + +function getAdDomain(bidResponse) { + if (bidResponse.meta && bidResponse.meta.advertiserDomains) { + let adomain = bidResponse.meta.advertiserDomains[0] + if (adomain) { + try { + let hostname = (new URL(adomain)); + return hostname.hostname.replace('www.', ''); + } catch (e) { + logWarn(LOG_PRE_FIX + 'Adomain URL (Not a proper URL):', adomain); + return adomain.replace('www.', ''); + } + } + } +} + function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { let bid = adUnit.bids[bidId]; partnerBids.push({ - 'pn': bid.bidder, + 'pn': getAdapterNameForAlias(bid.bidder), + 'bc': bid.bidder, 'bidid': bid.bidId, 'db': bid.bidResponse ? 0 : 1, 'kgpv': getValueForKgpv(bid, adUnitId), @@ -213,6 +233,7 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { 'dc': bid.bidResponse ? (bid.bidResponse.dealChannel || EMPTY_STRING) : EMPTY_STRING, 'l1': bid.bidResponse ? bid.clientLatencyTimeMs : 0, 'l2': 0, + 'adv': bid.bidResponse ? getAdDomain(bid.bidResponse) || undefined : undefined, 'ss': (s2sBidders.indexOf(bid.bidder) > -1) ? 1 : 0, 't': (bid.status == ERROR && bid.error.code == TIMEOUT_ERROR) ? 1 : 0, 'wb': (highestBid && highestBid.requestId === bid.bidId ? 1 : 0), @@ -287,6 +308,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { function executeBidWonLoggerCall(auctionId, adUnitId) { const winningBidId = cache.auctions[auctionId].adUnitCodes[adUnitId].bidWon; const winningBid = cache.auctions[auctionId].adUnitCodes[adUnitId].bids[winningBidId]; + const adapterName = getAdapterNameForAlias(winningBid.bidder); let pixelURL = END_POINT_WIN_BID_LOGGER; pixelURL += 'pubid=' + publisherId; pixelURL += '&purl=' + enc(config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''); @@ -296,7 +318,8 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&pid=' + enc(profileId); pixelURL += '&pdvid=' + enc(profileVersionId); pixelURL += '&slot=' + enc(adUnitId); - pixelURL += '&pn=' + enc(winningBid.bidder); + pixelURL += '&pn=' + enc(adapterName); + pixelURL += '&bc=' + enc(winningBid.bidder); pixelURL += '&en=' + enc(winningBid.bidResponse.bidPriceUSD); pixelURL += '&eg=' + enc(winningBid.bidResponse.bidGrossCpmUSD); pixelURL += '&kgpv=' + enc(getValueForKgpv(winningBid, adUnitId)); @@ -318,9 +341,9 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { function auctionInitHandler(args) { s2sBidders = (function() { let s2sConf = config.getConfig('s2sConfig'); - return (s2sConf && utils.isArray(s2sConf.bidders)) ? s2sConf.bidders : []; + return (s2sConf && isArray(s2sConf.bidders)) ? s2sConf.bidders : []; }()); - let cacheEntry = utils.pick(args, [ + let cacheEntry = pick(args, [ 'timestamp', 'timeout', 'bidderDonePendingCount', () => args.bidderRequests.length, @@ -346,7 +369,7 @@ function bidRequestedHandler(args) { function bidResponseHandler(args) { let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId]; if (!bid) { - utils.logError(LOG_PRE_FIX + 'Could not find associated bid request for bid response with requestId: ', args.requestId); + logError(LOG_PRE_FIX + 'Could not find associated bid request for bid response with requestId: ', args.requestId); return; } bid.source = formatSource(bid.source || args.source); @@ -397,7 +420,7 @@ function bidTimeoutHandler(args) { code: TIMEOUT_ERROR }; } else { - utils.logWarn(LOG_PRE_FIX + 'bid not found'); + logWarn(LOG_PRE_FIX + 'bid not found'); } }); } @@ -417,17 +440,17 @@ let pubmaticAdapter = Object.assign({}, baseAdapter, { profileId = Number(conf.options.profileId) || DEFAULT_PROFILE_ID; profileVersionId = Number(conf.options.profileVersionId) || DEFAULT_PROFILE_VERSION_ID; } else { - utils.logError(LOG_PRE_FIX + 'Config not found.'); + logError(LOG_PRE_FIX + 'Config not found.'); error = true; } if (!publisherId) { - utils.logError(LOG_PRE_FIX + 'Missing publisherId(Number).'); + logError(LOG_PRE_FIX + 'Missing publisherId(Number).'); error = true; } if (error) { - utils.logError(LOG_PRE_FIX + 'Not collecting data due to error(s).'); + logError(LOG_PRE_FIX + 'Not collecting data due to error(s).'); } else { baseAdapter.enableAnalytics.call(this, conf); } diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index c6ecea48abf..93afe96af78 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { logWarn, _each, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -7,16 +7,18 @@ import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'pubmatic'; const LOG_WARN_PREFIX = 'PubMatic: '; const ENDPOINT = 'https://hbopenbid.pubmatic.com/translator?source=prebid-client'; -const USER_SYNC_URL_IFRAME = 'https://ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p='; +const USER_SYNC_URL_IFRAME = 'https://ads.pubmatic.com/AdServer/js/user_sync.html?kdntuid=1&p='; const USER_SYNC_URL_IMAGE = 'https://image8.pubmatic.com/AdServer/ImgSync?p='; const DEFAULT_CURRENCY = 'USD'; const AUCTION_TYPE = 1; +const GROUPM_ALIAS = {code: 'groupm', gvlid: 98}; const UNDEFINED = undefined; const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html'; const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com) const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application +const MSG_VIDEO_PLACEMENT_MISSING = 'Video.Placement param missing'; const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url 'gender': '', // User gender @@ -48,7 +50,8 @@ const VIDEO_CUSTOM_PARAMS = { 'linearity': DATA_TYPES.NUMBER, 'placement': DATA_TYPES.NUMBER, 'minbitrate': DATA_TYPES.NUMBER, - 'maxbitrate': DATA_TYPES.NUMBER + 'maxbitrate': DATA_TYPES.NUMBER, + 'skip': DATA_TYPES.NUMBER } const NATIVE_ASSETS = { @@ -107,6 +110,11 @@ const dealChannelValues = { 5: 'PREF', 6: 'PMPG' }; + +const FLOC_FORMAT = { + 'EID': 1, + 'SEGMENT': 2 +} // BB stands for Blue BillyWig const BB_RENDERER = { bootstrapPlayer: function(bid) { @@ -118,7 +126,7 @@ const BB_RENDERER = { else if (bid.vastUrl) config.vastUrl = bid.vastUrl; if (!bid.vastXml && !bid.vastUrl) { - utils.logWarn(`${LOG_WARN_PREFIX}: No vastXml or vastUrl on bid, bailing...`); + logWarn(`${LOG_WARN_PREFIX}: No vastXml or vastUrl on bid, bailing...`); return; } @@ -136,7 +144,7 @@ const BB_RENDERER = { } if (renderer) renderer.bootstrap(config, ele); - else utils.logWarn(`${LOG_WARN_PREFIX}: Couldn't find a renderer with ${rendererId}`); + else logWarn(`${LOG_WARN_PREFIX}: Couldn't find a renderer with ${rendererId}`); }, newRenderer: function(rendererCode, adUnitCode) { var rendererUrl = RENDERER_URL.replace('$RENDERER', rendererCode); @@ -149,7 +157,7 @@ const BB_RENDERER = { try { renderer.setRender(BB_RENDERER.outstreamRender); } catch (err) { - utils.logWarn(`${LOG_WARN_PREFIX}: Error tying to setRender on renderer`, err); + logWarn(`${LOG_WARN_PREFIX}: Error tying to setRender on renderer`, err); } return renderer; @@ -162,15 +170,21 @@ const BB_RENDERER = { } }; +const MEDIATYPE = [ + BANNER, + VIDEO, + NATIVE +] + let publisherId = 0; let isInvalidNativeRequest = false; let NATIVE_ASSET_ID_TO_KEY_MAP = {}; let NATIVE_ASSET_KEY_TO_ASSET_MAP = {}; // loading NATIVE_ASSET_ID_TO_KEY_MAP -utils._each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAsset.KEY }); +_each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAsset.KEY }); // loading NATIVE_ASSET_KEY_TO_ASSET_MAP -utils._each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); +_each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); function _getDomainFromURL(url) { let anchor = document.createElement('a'); @@ -179,8 +193,8 @@ function _getDomainFromURL(url) { } function _parseSlotParam(paramName, paramValue) { - if (!utils.isStr(paramValue)) { - paramValue && utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); + if (!isStr(paramValue)) { + paramValue && logWarn(LOG_WARN_PREFIX + 'Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); return UNDEFINED; } @@ -201,11 +215,11 @@ function _parseSlotParam(paramName, paramValue) { } function _cleanSlot(slotName) { - if (utils.isStr(slotName)) { + if (isStr(slotName)) { return slotName.replace(/^\s+/g, '').replace(/\s+$/g, ''); } if (slotName) { - utils.logWarn(BIDDER_CODE + ': adSlot must be a string. Ignoring adSlot'); + logWarn(BIDDER_CODE + ': adSlot must be a string. Ignoring adSlot'); } return ''; } @@ -231,7 +245,7 @@ function _parseAdSlot(bid) { // i.e size is specified in adslot, so consider that and ignore sizes array splits = splits[1].split('x'); if (splits.length != 2) { - utils.logWarn(LOG_WARN_PREFIX + 'AdSlot Error: adSlot not in required format'); + logWarn(LOG_WARN_PREFIX + 'AdSlot Error: adSlot not in required format'); return; } bid.params.width = parseInt(splits[0], 10); @@ -282,10 +296,10 @@ function _handleCustomParams(params, conf) { value = entry.f(value, conf); } - if (utils.isStr(value)) { + if (isStr(value)) { conf[key] = value; } else { - utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); + logWarn(LOG_WARN_PREFIX + 'Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); } } } @@ -322,22 +336,22 @@ function _checkParamDataType(key, value, datatype) { var functionToExecute; switch (datatype) { case DATA_TYPES.BOOLEAN: - functionToExecute = utils.isBoolean; + functionToExecute = isBoolean; break; case DATA_TYPES.NUMBER: - functionToExecute = utils.isNumber; + functionToExecute = isNumber; break; case DATA_TYPES.STRING: - functionToExecute = utils.isStr; + functionToExecute = isStr; break; case DATA_TYPES.ARRAY: - functionToExecute = utils.isArray; + functionToExecute = isArray; break; } if (functionToExecute(value)) { return value; } - utils.logWarn(LOG_WARN_PREFIX + errMsg); + logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } @@ -374,7 +388,7 @@ function _createNativeRequest(params) { } }; } else { - utils.logWarn(LOG_WARN_PREFIX + 'Error: Title Length is required for native ad: ' + JSON.stringify(params)); + logWarn(LOG_WARN_PREFIX + 'Error: Title Length is required for native ad: ' + JSON.stringify(params)); } break; case NATIVE_ASSETS.IMAGE.KEY: @@ -393,7 +407,7 @@ function _createNativeRequest(params) { } }; } else { - utils.logWarn(LOG_WARN_PREFIX + 'Error: Image sizes is required for native ad: ' + JSON.stringify(params)); + logWarn(LOG_WARN_PREFIX + 'Error: Image sizes is required for native ad: ' + JSON.stringify(params)); } break; case NATIVE_ASSETS.ICON.KEY: @@ -408,7 +422,7 @@ function _createNativeRequest(params) { } }; } else { - utils.logWarn(LOG_WARN_PREFIX + 'Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); + logWarn(LOG_WARN_PREFIX + 'Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); }; break; case NATIVE_ASSETS.VIDEO.KEY: @@ -488,13 +502,13 @@ function _createBannerRequest(bid) { var sizes = bid.mediaTypes.banner.sizes; var format = []; var bannerObj; - if (sizes !== UNDEFINED && utils.isArray(sizes)) { + if (sizes !== UNDEFINED && isArray(sizes)) { bannerObj = {}; if (!bid.params.width && !bid.params.height) { if (sizes.length === 0) { // i.e. since bid.params does not have width or height, and length of sizes is 0, need to ignore this banner imp bannerObj = UNDEFINED; - utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); + logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); return bannerObj; } else { bannerObj.w = parseInt(sizes[0][0], 10); @@ -517,41 +531,44 @@ function _createBannerRequest(bid) { } } bannerObj.pos = 0; - bannerObj.topframe = utils.inIframe() ? 0 : 1; + bannerObj.topframe = inIframe() ? 0 : 1; } else { - utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); + logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); bannerObj = UNDEFINED; } return bannerObj; } +export function checkVideoPlacement(videoData, adUnitCode) { + // Check for video.placement property. If property is missing display log message. + if (!deepAccess(videoData, 'placement')) { + logWarn(MSG_VIDEO_PLACEMENT_MISSING + ' for ' + adUnitCode); + }; +} + function _createVideoRequest(bid) { - var videoData = bid.params.video; + var videoData = mergeDeep(deepAccess(bid.mediaTypes, 'video'), bid.params.video); var videoObj; if (videoData !== UNDEFINED) { videoObj = {}; + checkVideoPlacement(videoData, bid.adUnitCode); for (var key in VIDEO_CUSTOM_PARAMS) { if (videoData.hasOwnProperty(key)) { videoObj[key] = _checkParamDataType(key, videoData[key], VIDEO_CUSTOM_PARAMS[key]); } } // read playersize and assign to h and w. - if (utils.isArray(bid.mediaTypes.video.playerSize[0])) { + if (isArray(bid.mediaTypes.video.playerSize[0])) { videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0][0], 10); videoObj.h = parseInt(bid.mediaTypes.video.playerSize[0][1], 10); - } else if (utils.isNumber(bid.mediaTypes.video.playerSize[0])) { + } else if (isNumber(bid.mediaTypes.video.playerSize[0])) { videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0], 10); videoObj.h = parseInt(bid.mediaTypes.video.playerSize[1], 10); } - if (bid.params.video.hasOwnProperty('skippable')) { - videoObj.ext = { - 'video_skippable': bid.params.video.skippable ? 1 : 0 - }; - } } else { videoObj = UNDEFINED; - utils.logWarn(LOG_WARN_PREFIX + 'Error: Video config params missing for adunit: ' + bid.params.adUnit + ' with mediaType set as video. Ignoring video impression in the adunit.'); + logWarn(LOG_WARN_PREFIX + 'Error: Video config params missing for adunit: ' + bid.params.adUnit + ' with mediaType set as video. Ignoring video impression in the adunit.'); } return videoObj; } @@ -559,23 +576,70 @@ function _createVideoRequest(bid) { // support for PMP deals function _addPMPDealsInImpression(impObj, bid) { if (bid.params.deals) { - if (utils.isArray(bid.params.deals)) { + if (isArray(bid.params.deals)) { bid.params.deals.forEach(function(dealId) { - if (utils.isStr(dealId) && dealId.length > 3) { + if (isStr(dealId) && dealId.length > 3) { if (!impObj.pmp) { impObj.pmp = { private_auction: 0, deals: [] }; } impObj.pmp.deals.push({ id: dealId }); } else { - utils.logWarn(LOG_WARN_PREFIX + 'Error: deal-id present in array bid.params.deals should be a strings with more than 3 charaters length, deal-id ignored: ' + dealId); + logWarn(LOG_WARN_PREFIX + 'Error: deal-id present in array bid.params.deals should be a strings with more than 3 charaters length, deal-id ignored: ' + dealId); } }); } else { - utils.logWarn(LOG_WARN_PREFIX + 'Error: bid.params.deals should be an array of strings.'); + logWarn(LOG_WARN_PREFIX + 'Error: bid.params.deals should be an array of strings.'); } } } +function _addDealCustomTargetings(imp, bid) { + var dctr = ''; + var dctrLen; + if (bid.params.dctr) { + dctr = bid.params.dctr; + if (isStr(dctr) && dctr.length > 0) { + var arr = dctr.split('|'); + dctr = ''; + arr.forEach(val => { + dctr += (val.length > 0) ? (val.trim() + '|') : ''; + }); + dctrLen = dctr.length; + if (dctr.substring(dctrLen, dctrLen - 1) === '|') { + dctr = dctr.substring(0, dctrLen - 1); + } + imp.ext['key_val'] = dctr.trim() + } else { + logWarn(LOG_WARN_PREFIX + 'Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value'); + } + } +} + +function _addJWPlayerSegmentData(imp, bid, isS2S) { + var jwSegData = (bid.rtd && bid.rtd.jwplayer && bid.rtd.jwplayer.targeting) || undefined; + var jwPlayerData = ''; + const jwMark = 'jw-'; + + if (jwSegData === undefined || jwSegData === '' || !jwSegData.hasOwnProperty('segments')) return; + + var maxLength = jwSegData.segments.length; + + jwPlayerData += jwMark + 'id=' + jwSegData.content.id; // add the content id first + + for (var i = 0; i < maxLength; i++) { + jwPlayerData += '|' + jwMark + jwSegData.segments[i] + '=1'; + } + + var ext; + + if (isS2S) { + (imp.dctr === undefined || imp.dctr.length == 0) ? imp.dctr = jwPlayerData : imp.dctr += '|' + jwPlayerData; + } else { + ext = imp.ext; + ext && ext.key_val === undefined ? ext.key_val = jwPlayerData : ext.key_val += '|' + jwPlayerData; + } +} + function _createImpressionObject(bid, conf) { var impObj = {}; var bannerObj; @@ -597,7 +661,8 @@ function _createImpressionObject(bid, conf) { }; _addPMPDealsInImpression(impObj, bid); - + _addDealCustomTargetings(impObj, bid); + _addJWPlayerSegmentData(impObj, bid); if (bid.hasOwnProperty('mediaTypes')) { for (mediaTypes in bid.mediaTypes) { switch (mediaTypes) { @@ -612,7 +677,7 @@ function _createImpressionObject(bid, conf) { if (!isInvalidNativeRequest) { impObj.native = nativeObj; } else { - utils.logWarn(LOG_WARN_PREFIX + 'Error: Error in Native adunit ' + bid.params.adUnit + '. Ignoring the adunit. Refer to ' + PREBID_NATIVE_HELP_LINK + ' for more details.'); + logWarn(LOG_WARN_PREFIX + 'Error: Error in Native adunit ' + bid.params.adUnit + '. Ignoring the adunit. Refer to ' + PREBID_NATIVE_HELP_LINK + ' for more details.'); } break; case VIDEO: @@ -630,9 +695,9 @@ function _createImpressionObject(bid, conf) { pos: 0, w: bid.params.width, h: bid.params.height, - topframe: utils.inIframe() ? 0 : 1 + topframe: inIframe() ? 0 : 1 }; - if (utils.isArray(sizes) && sizes.length > 1) { + if (isArray(sizes) && sizes.length > 1) { sizes = sizes.splice(1, sizes.length - 1); sizes.forEach(size => { format.push({ @@ -655,31 +720,31 @@ function _createImpressionObject(bid, conf) { } function _addImpressionFPD(imp, bid) { - const ortb2 = {...utils.deepAccess(bid, 'ortb2Imp.ext.data')}; + const ortb2 = {...deepAccess(bid, 'ortb2Imp.ext.data')}; Object.keys(ortb2).forEach(prop => { /** * Prebid AdSlot * @type {(string|undefined)} */ if (prop === 'pbadslot') { - if (typeof ortb2[prop] === 'string' && ortb2[prop]) utils.deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); + if (typeof ortb2[prop] === 'string' && ortb2[prop]) deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); } else if (prop === 'adserver') { /** * Copy GAM AdUnit and Name to imp */ ['name', 'adslot'].forEach(name => { /** @type {(string|undefined)} */ - const value = utils.deepAccess(ortb2, `adserver.${name}`); + const value = deepAccess(ortb2, `adserver.${name}`); if (typeof value === 'string' && value) { - utils.deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); + deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); // copy GAM ad unit id as imp[].ext.dfp_ad_unit_code if (name === 'adslot') { - utils.deepSetValue(imp, `ext.dfp_ad_unit_code`, value); + deepSetValue(imp, `ext.dfp_ad_unit_code`, value); } } }); } else { - utils.deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); + deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); } }); } @@ -690,47 +755,138 @@ function _addFloorFromFloorModule(impObj, bid) { if (typeof bid.getFloor === 'function' && !config.getConfig('pubmatic.disableFloors')) { [BANNER, VIDEO, NATIVE].forEach(mediaType => { if (impObj.hasOwnProperty(mediaType)) { - let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: '*' }); - if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { - let mediaTypeFloor = parseFloat(floorInfo.floor); - bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor)) + let sizesArray = []; + + if (mediaType === 'banner') { + if (impObj[mediaType].w && impObj[mediaType].h) { + sizesArray.push([impObj[mediaType].w, impObj[mediaType].h]); + } + if (isArray(impObj[mediaType].format)) { + impObj[mediaType].format.forEach(size => sizesArray.push([size.w, size.h])); + } + } + + if (sizesArray.length === 0) { + sizesArray.push('*') } + + sizesArray.forEach(size => { + let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: size }); + logInfo(LOG_WARN_PREFIX, 'floor from floor module returned for mediatype:', mediaType, ' and size:', size, ' is: currency', floorInfo.currency, 'floor', floorInfo.floor); + if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { + let mediaTypeFloor = parseFloat(floorInfo.floor); + logInfo(LOG_WARN_PREFIX, 'floor from floor module:', mediaTypeFloor, 'previous floor value', bidFloor, 'Min:', Math.min(mediaTypeFloor, bidFloor)); + if (bidFloor === -1) { + bidFloor = mediaTypeFloor; + } else { + bidFloor = Math.min(mediaTypeFloor, bidFloor) + } + logInfo(LOG_WARN_PREFIX, 'new floor value:', bidFloor); + } + }); } }); } // get highest from impObj.bidfllor and floor from floor module // as we are using Math.max, it is ok if we have not got any floor from floorModule, then value of bidFloor will be -1 if (impObj.bidfloor) { + logInfo(LOG_WARN_PREFIX, 'floor from floor module:', bidFloor, 'impObj.bidfloor', impObj.bidfloor, 'Max:', Math.max(bidFloor, impObj.bidfloor)); bidFloor = Math.max(bidFloor, impObj.bidfloor) } // assign value only if bidFloor is > 0 impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : UNDEFINED); + logInfo(LOG_WARN_PREFIX, 'new impObj.bidfloor value:', impObj.bidfloor); +} + +function _getFlocId(validBidRequests, flocFormat) { + var flocIdObject = null; + var flocId = deepAccess(validBidRequests, '0.userId.flocId'); + if (flocId && flocId.id) { + switch (flocFormat) { + case FLOC_FORMAT.SEGMENT: + flocIdObject = { + id: 'FLOC', + name: 'FLOC', + ext: { + ver: flocId.version + }, + segment: [{ + id: flocId.id, + name: 'chrome.com', + value: flocId.id.toString() + }] + } + break; + case FLOC_FORMAT.EID: + default: + flocIdObject = { + source: 'chrome.com', + uids: [ + { + atype: 1, + id: flocId.id, + ext: { + ver: flocId.version + } + }, + ] + } + break; + } + } + return flocIdObject; +} + +function _handleFlocId(payload, validBidRequests) { + var flocObject = _getFlocId(validBidRequests, FLOC_FORMAT.SEGMENT); + if (flocObject) { + if (!payload.user) { + payload.user = {}; + } + if (!payload.user.data) { + payload.user.data = []; + } + payload.user.data.push(flocObject); + } } function _handleEids(payload, validBidRequests) { - const bidUserIdAsEids = utils.deepAccess(validBidRequests, '0.userIdAsEids'); - if (utils.isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { - utils.deepSetValue(payload, 'user.eids', bidUserIdAsEids); + let bidUserIdAsEids = deepAccess(validBidRequests, '0.userIdAsEids'); + let flocObject = _getFlocId(validBidRequests, FLOC_FORMAT.EID); + if (flocObject) { + if (!bidUserIdAsEids) { + bidUserIdAsEids = []; + } + bidUserIdAsEids.push(flocObject); + } + if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + deepSetValue(payload, 'user.eids', bidUserIdAsEids); } } -function _checkMediaType(adm, newBid) { +function _checkMediaType(bid, newBid) { // Create a regex here to check the strings - var admStr = ''; - var videoRegex = new RegExp(/VAST\s+version/); - if (adm.indexOf('span class="PubAPIAd"') >= 0) { - newBid.mediaType = BANNER; - } else if (videoRegex.test(adm)) { - newBid.mediaType = VIDEO; + if (bid.ext && bid.ext['bidtype'] != undefined) { + newBid.mediaType = MEDIATYPE[bid.ext.bidtype]; } else { - try { - admStr = JSON.parse(adm.replace(/\\/g, '')); - if (admStr && admStr.native) { - newBid.mediaType = NATIVE; + logInfo(LOG_WARN_PREFIX + 'bid.ext.bidtype does not exist, checking alternatively for mediaType') + var adm = bid.adm; + var admStr = ''; + var videoRegex = new RegExp(/VAST\s+version/); + if (adm.indexOf('span class="PubAPIAd"') >= 0) { + newBid.mediaType = BANNER; + } else if (videoRegex.test(adm)) { + newBid.mediaType = VIDEO; + } else { + try { + admStr = JSON.parse(adm.replace(/\\/g, '')); + if (admStr && admStr.native) { + newBid.mediaType = NATIVE; + } + } catch (e) { + logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + adm); } - } catch (e) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + adm); } } } @@ -742,7 +898,7 @@ function _parseNativeResponse(bid, newBid) { try { adm = JSON.parse(bid.adm.replace(/\\/g, '')); } catch (ex) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); + logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); return; } if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { @@ -802,7 +958,7 @@ function _blockedIabCategoriesValidation(payload, blockedIabCategories) { if (typeof category === 'string') { // only strings return true; } else { - utils.logWarn(LOG_WARN_PREFIX + 'bcat: Each category should be a string, ignoring category: ' + category); + logWarn(LOG_WARN_PREFIX + 'bcat: Each category should be a string, ignoring category: ' + category); return false; } }) @@ -811,47 +967,15 @@ function _blockedIabCategoriesValidation(payload, blockedIabCategories) { if (category.length > 3) { return arr.indexOf(category) === index; // unique value only } else { - utils.logWarn(LOG_WARN_PREFIX + 'bcat: Each category should have a value of a length of more than 3 characters, ignoring category: ' + category) + logWarn(LOG_WARN_PREFIX + 'bcat: Each category should have a value of a length of more than 3 characters, ignoring category: ' + category) } }); if (blockedIabCategories.length > 0) { - utils.logWarn(LOG_WARN_PREFIX + 'bcat: Selected: ', blockedIabCategories); + logWarn(LOG_WARN_PREFIX + 'bcat: Selected: ', blockedIabCategories); payload.bcat = blockedIabCategories; } } -function _handleDealCustomTargetings(payload, dctrArr, validBidRequests) { - var dctr = ''; - var dctrLen; - // set dctr value in site.ext, if present in validBidRequests[0], else ignore - if (dctrArr.length > 0) { - if (validBidRequests[0].params.hasOwnProperty('dctr')) { - dctr = validBidRequests[0].params.dctr; - if (utils.isStr(dctr) && dctr.length > 0) { - var arr = dctr.split('|'); - dctr = ''; - arr.forEach(val => { - dctr += (val.length > 0) ? (val.trim() + '|') : ''; - }); - dctrLen = dctr.length; - if (dctr.substring(dctrLen, dctrLen - 1) === '|') { - dctr = dctr.substring(0, dctrLen - 1); - } - payload.site.ext = { - key_val: dctr.trim() - } - } else { - utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value'); - } - if (dctrArr.length > 1) { - utils.logWarn(LOG_WARN_PREFIX + 'dctr value found in more than 1 adunits. Value from 1st adunit will be picked. Ignoring values from subsequent adunits'); - } - } else { - utils.logWarn(LOG_WARN_PREFIX + 'dctr value not found in 1st adunit, ignoring values from subsequent adunits'); - } - } -} - function _assignRenderer(newBid, request) { let bidParams, context, adUnitCode; if (request.bidderRequest && request.bidderRequest.bids) { @@ -867,12 +991,22 @@ function _assignRenderer(newBid, request) { newBid.renderer = BB_RENDERER.newRenderer(newBid.rendererCode, adUnitCode); } } -}; +} + +function isNonEmptyArray(test) { + if (isArray(test) === true) { + if (test.length > 0) { + return true; + } + } + return false; +} export const spec = { code: BIDDER_CODE, gvlid: 76, supportedMediaTypes: [BANNER, VIDEO, NATIVE], + aliases: [GROUPM_ALIAS], /** * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid * @@ -881,28 +1015,42 @@ export const spec = { */ isBidRequestValid: bid => { if (bid && bid.params) { - if (!utils.isStr(bid.params.publisherId)) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric (wrap it in quotes in your config). Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + if (!isStr(bid.params.publisherId)) { + logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric (wrap it in quotes in your config). Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); return false; } // video ad validation - if (bid.params.hasOwnProperty('video')) { - if (!bid.params.video.hasOwnProperty('mimes') || !utils.isArray(bid.params.video.mimes) || bid.params.video.mimes.length === 0) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + // bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array + let mediaTypesVideoMimes = deepAccess(bid.mediaTypes, 'video.mimes'); + let paramsVideoMimes = deepAccess(bid, 'params.video.mimes'); + if (isNonEmptyArray(mediaTypesVideoMimes) === false && isNonEmptyArray(paramsVideoMimes) === false) { + logWarn(LOG_WARN_PREFIX + 'Error: For video ads, bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); return false; } - if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { - if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { - utils.logError(`${LOG_WARN_PREFIX}: no context specified in bid. Rejecting bid: `, bid); - return false; - } - if (bid.mediaTypes[VIDEO].context === 'outstream' && !utils.isStr(bid.params.outstreamAU) && !bid.hasOwnProperty('renderer') && !bid.mediaTypes[VIDEO].hasOwnProperty('renderer')) { - utils.logError(`${LOG_WARN_PREFIX}: for "outstream" bids either outstreamAU parameter must be provided or ad unit supplied renderer is required. Rejecting bid: `, bid); + + if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { + logError(`${LOG_WARN_PREFIX}: no context specified in bid. Rejecting bid: `, bid); + return false; + } + + if (bid.mediaTypes[VIDEO].context === 'outstream' && + !isStr(bid.params.outstreamAU) && + !bid.hasOwnProperty('renderer') && + !bid.mediaTypes[VIDEO].hasOwnProperty('renderer')) { + // we are here since outstream ad-unit is provided without outstreamAU and renderer + // so it is not a valid video ad-unit + // but it may be valid banner or native ad-unit + // so if mediaType banner or Native is present then we will remove media-type video and return true + + if (bid.mediaTypes.hasOwnProperty(BANNER) || bid.mediaTypes.hasOwnProperty(NATIVE)) { + delete bid.mediaTypes[VIDEO]; + logWarn(`${LOG_WARN_PREFIX}: for "outstream" bids either outstreamAU parameter must be provided or ad unit supplied renderer is required. Rejecting mediatype Video of bid: `, bid); + return true; + } else { + logError(`${LOG_WARN_PREFIX}: for "outstream" bids either outstreamAU parameter must be provided or ad unit supplied renderer is required. Rejecting bid: `, bid); return false; } - } else { - utils.logError(`${LOG_WARN_PREFIX}: mediaTypes or mediaTypes.video is not specified. Rejecting bid: `, bid); - return false; } } return true; @@ -929,16 +1077,16 @@ export const spec = { var blockedIabCategories = []; validBidRequests.forEach(originalBid => { - bid = utils.deepClone(originalBid); + bid = deepClone(originalBid); bid.params.adSlot = bid.params.adSlot || ''; _parseAdSlot(bid); - if (bid.params.hasOwnProperty('video')) { + if ((bid.mediaTypes && bid.mediaTypes.hasOwnProperty('video')) || bid.params.hasOwnProperty('video')) { // Nothing to do } else { // If we have a native mediaType configured alongside banner, its ok if the banner size is not set in width and height // The corresponding banner imp object will not be generated, but we still want the native object to be sent, hence the following check if (!(bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(NATIVE)) && bid.params.width === 0 && bid.params.height === 0) { - utils.logWarn(LOG_WARN_PREFIX + 'Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); + logWarn(LOG_WARN_PREFIX + 'Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); return; } } @@ -948,14 +1096,14 @@ export const spec = { if (bidCurrency === '') { bidCurrency = bid.params.currency || UNDEFINED; } else if (bid.params.hasOwnProperty('currency') && bidCurrency !== bid.params.currency) { - utils.logWarn(LOG_WARN_PREFIX + 'Currency specifier ignored. Only one currency permitted.'); + logWarn(LOG_WARN_PREFIX + 'Currency specifier ignored. Only one currency permitted.'); } bid.params.currency = bidCurrency; // check if dctr is added to more than 1 adunit - if (bid.params.hasOwnProperty('dctr') && utils.isStr(bid.params.dctr)) { + if (bid.params.hasOwnProperty('dctr') && isStr(bid.params.dctr)) { dctrArr.push(bid.params.dctr); } - if (bid.params.hasOwnProperty('bcat') && utils.isArray(bid.params.bcat)) { + if (bid.params.hasOwnProperty('bcat') && isArray(bid.params.bcat)) { blockedIabCategories = blockedIabCategories.concat(bid.params.bcat); } var impObj = _createImpressionObject(bid, conf); @@ -998,7 +1146,7 @@ export const spec = { } // passing transactionId in source.tid - utils.deepSetValue(payload, 'source.tid', conf.transactionId); + deepSetValue(payload, 'source.tid', conf.transactionId); // test bids if (window.location.href.indexOf('pubmaticTest=true') !== -1) { @@ -1007,36 +1155,35 @@ export const spec = { // adding schain object if (validBidRequests[0].schain) { - utils.deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); + deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); } // Attaching GDPR Consent Params if (bidderRequest && bidderRequest.gdprConsent) { - utils.deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - utils.deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } // CCPA if (bidderRequest && bidderRequest.uspConsent) { - utils.deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } // coppa compliance if (config.getConfig('coppa') === true) { - utils.deepSetValue(payload, 'regs.coppa', 1); + deepSetValue(payload, 'regs.coppa', 1); } - _handleDealCustomTargetings(payload, dctrArr, validBidRequests); _handleEids(payload, validBidRequests); _blockedIabCategoriesValidation(payload, blockedIabCategories); - + _handleFlocId(payload, validBidRequests); // First Party Data const commonFpd = config.getConfig('ortb2') || {}; if (commonFpd.site) { - utils.mergeDeep(payload, {site: commonFpd.site}); + mergeDeep(payload, {site: commonFpd.site}); } if (commonFpd.user) { - utils.mergeDeep(payload, {user: commonFpd.user}); + mergeDeep(payload, {user: commonFpd.user}); } // Note: Do not move this block up @@ -1074,12 +1221,12 @@ export const spec = { let parsedRequest = JSON.parse(request.data); let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : ''; try { - if (response.body && response.body.seatbid && utils.isArray(response.body.seatbid)) { + if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { // Supporting multiple bid responses for same adSize respCur = response.body.cur || respCur; response.body.seatbid.forEach(seatbidder => { seatbidder.bid && - utils.isArray(seatbidder.bid) && + isArray(seatbidder.bid) && seatbidder.bid.forEach(bid => { let newBid = { requestId: bid.impid, @@ -1100,7 +1247,7 @@ export const spec = { if (parsedRequest.imp && parsedRequest.imp.length > 0) { parsedRequest.imp.forEach(req => { if (bid.impid === req.id) { - _checkMediaType(bid.adm, newBid); + _checkMediaType(bid, newBid); switch (newBid.mediaType) { case BANNER: break; @@ -1145,7 +1292,7 @@ export const spec = { }); } } catch (error) { - utils.logError(error); + logError(error); } return bidResponses; }, @@ -1191,8 +1338,10 @@ export const spec = { * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol * @return {Object} params bid params */ - transformBidParams: function (params, isOpenRtb) { - return utils.convertTypes({ + + transformBidParams: function (params, isOpenRtb, adUnit, bidRequests) { + _addJWPlayerSegmentData(params, adUnit.bids[0], true); + return convertTypes({ 'publisherId': 'string', 'adSlot': 'string' }, params); diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md index e9d93d79758..baf58177505 100644 --- a/modules/pubmaticBidAdapter.md +++ b/modules/pubmaticBidAdapter.md @@ -25,7 +25,7 @@ var adUnits = [ bidder: 'pubmatic', params: { publisherId: '156209', // required, must be a string, not an integer or other js type. - oustreamAU: 'renderer_test_pubmatic', // required if mediaTypes-> video-> context is 'outstream' and optional if renderer is defined in adUnits or in mediaType video. This value can be get by BlueBillyWig Team. + outstreamAU: 'renderer_test_pubmatic', // required if mediaTypes-> video-> context is 'outstream' and optional if renderer is defined in adUnits or in mediaType video. This value can be get by BlueBillyWig Team. adSlot: 'pubmatic_test2', // optional, must be a string, not an integer or other js type. pmzoneid: 'zone1, zone11', // optional lat: '40.712775', // optional diff --git a/modules/pubperfAnalyticsAdapter.js b/modules/pubperfAnalyticsAdapter.js index 800ea1cd550..9282d5814c0 100644 --- a/modules/pubperfAnalyticsAdapter.js +++ b/modules/pubperfAnalyticsAdapter.js @@ -4,7 +4,7 @@ import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import * as utils from '../src/utils.js'; +import { logError } from '../src/utils.js'; var pubperfAdapter = adapter({ global: 'pubperf_pbjs', @@ -16,11 +16,11 @@ pubperfAdapter.originEnableAnalytics = pubperfAdapter.enableAnalytics; pubperfAdapter.enableAnalytics = config => { if (!config || !config.provider || config.provider !== 'pubperf') { - utils.logError('expected config.provider to equal pubperf'); + logError('expected config.provider to equal pubperf'); return; } if (!window['pubperf_pbjs']) { - utils.logError( + logError( `Make sure that Pubperf tag from https://www.pubperf.com is included before the Prebid configuration.` ); return; diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js index fe217454b88..006d6da7eb7 100644 --- a/modules/pubwiseAnalyticsAdapter.js +++ b/modules/pubwiseAnalyticsAdapter.js @@ -1,9 +1,9 @@ +import { getParameterByName, logInfo, generateUUID, debugTurnedOn } 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 { getStorageManager } from '../src/storageManager.js'; -const utils = require('../src/utils.js'); const storage = getStorageManager(); /**** @@ -76,7 +76,7 @@ function enrichWithUTM(dataBag) { let newUtm = false; try { for (let prop in utmKeys) { - utmKeys[prop] = utils.getParameterByName(prop); + utmKeys[prop] = getParameterByName(prop); if (utmKeys[prop]) { newUtm = true; dataBag[prop] = utmKeys[prop]; @@ -192,7 +192,7 @@ function markEnabled() { } function pwInfo(info, context) { - utils.logInfo(`${analyticsName} ` + info, context); + logInfo(`${analyticsName} ` + info, context); } function filterBidResponse(data) { @@ -304,7 +304,7 @@ pubwiseAnalytics.storeSessionID = function (userSessID) { // ensure a session exists, if not make one, always store it pubwiseAnalytics.ensureSession = function () { if (sessionExpired() === true || userSessionID() === null || userSessionID() === '') { - let generatedId = utils.generateUUID(); + let generatedId = generateUUID(); expireUtmData(); this.storeSessionID(generatedId); sessionData.sessionId = generatedId; @@ -320,10 +320,10 @@ pubwiseAnalytics.enableAnalytics = function (config) { configOptions = Object.assign(configOptions, config.options); // take the PBJS debug for our debug setting if no PW debug is defined if (configOptions.debug === null) { - configOptions.debug = utils.debugTurnedOn(); + configOptions.debug = debugTurnedOn(); } markEnabled(); - sessionData.activationId = utils.generateUUID(); + sessionData.activationId = generateUUID(); this.ensureSession(); pubwiseAnalytics.adapterEnableAnalytics(config); }; diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index f450a8bede8..a1b9ffb56a0 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { _each, isStr, deepClone, isArray, deepSetValue, inIframe, logMessage, logInfo, logWarn, logError } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; @@ -81,9 +81,9 @@ let NATIVE_ASSET_KEY_TO_ASSET_MAP = {}; // together allows traversal of NATIVE_ASSETS_LIST in any direction // id -> key -utils._each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAsset.KEY }); +_each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAsset.KEY }); // key -> asset -utils._each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); +_each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); export const spec = { code: BIDDER_CODE, @@ -99,7 +99,7 @@ export const spec = { // siteId is required if (bid.params && bid.params.siteId) { // it must be a string - if (!utils.isStr(bid.params.siteId)) { + if (!isStr(bid.params.siteId)) { _logWarn('siteId is required for bid', bid); return false; } @@ -127,7 +127,7 @@ export const spec = { var blockedIabCategories = []; validBidRequests.forEach(originalBid => { - bid = utils.deepClone(originalBid); + bid = deepClone(originalBid); bid.params.adSlot = bid.params.adSlot || ''; _parseAdSlot(bid); @@ -136,7 +136,7 @@ export const spec = { bidCurrency = bid.params.currency || UNDEFINED; bid.params.currency = bidCurrency; - if (bid.params.hasOwnProperty('bcat') && utils.isArray(bid.params.bcat)) { + if (bid.params.hasOwnProperty('bcat') && isArray(bid.params.bcat)) { blockedIabCategories = blockedIabCategories.concat(bid.params.bcat); } @@ -180,27 +180,27 @@ export const spec = { } // passing transactionId in source.tid - utils.deepSetValue(payload, 'source.tid', conf.transactionId); + deepSetValue(payload, 'source.tid', conf.transactionId); // schain if (validBidRequests[0].schain) { - utils.deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); + deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); } // gdpr consent if (bidderRequest && bidderRequest.gdprConsent) { - utils.deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - utils.deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } // ccpa on the root object if (bidderRequest && bidderRequest.uspConsent) { - utils.deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } // if coppa is in effect then note it if (config.getConfig('coppa') === true) { - utils.deepSetValue(payload, 'regs.coppa', 1); + deepSetValue(payload, 'regs.coppa', 1); } var options = {contentType: 'text/plain'} @@ -230,12 +230,12 @@ export const spec = { // let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : ''; // try { - if (response.body && response.body.seatbid && utils.isArray(response.body.seatbid)) { + if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { // Supporting multiple bid responses for same adSize respCur = response.body.cur || respCur; response.body.seatbid.forEach(seatbidder => { seatbidder.bid && - utils.isArray(seatbidder.bid) && + isArray(seatbidder.bid) && seatbidder.bid.forEach(bid => { let newBid = { requestId: bid.impid, @@ -388,7 +388,7 @@ function _handleCustomParams(params, conf) { value = entry.f(value, conf); } - if (utils.isStr(value)) { + if (isStr(value)) { conf[key] = value; } else { _logWarn('Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); @@ -469,7 +469,7 @@ function _createImpressionObject(bid, conf) { } function _parseSlotParam(paramName, paramValue) { - if (!utils.isStr(paramValue)) { + if (!isStr(paramValue)) { paramValue && _logWarn('Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); return UNDEFINED; } @@ -520,7 +520,7 @@ function _parseAdSlot(bid) { } function _cleanSlotName(slotName) { - if (utils.isStr(slotName)) { + if (isStr(slotName)) { return slotName.replace(/^\s+/g, '').replace(/\s+$/g, ''); } return ''; @@ -705,7 +705,7 @@ function _createBannerRequest(bid) { var sizes = bid.mediaTypes.banner.sizes; var format = []; var bannerObj; - if (sizes !== UNDEFINED && utils.isArray(sizes)) { + if (sizes !== UNDEFINED && isArray(sizes)) { bannerObj = {}; if (!bid.params.width && !bid.params.height) { if (sizes.length === 0) { @@ -734,7 +734,7 @@ function _createBannerRequest(bid) { } } bannerObj.pos = 0; - bannerObj.topframe = utils.inIframe() ? 0 : 1; + bannerObj.topframe = inIframe() ? 0 : 1; } else { _logWarn('Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); bannerObj = UNDEFINED; @@ -745,22 +745,22 @@ function _createBannerRequest(bid) { // various error levels are not always used // eslint-disable-next-line no-unused-vars function _logMessage(textValue, objectValue) { - utils.logMessage('PubWise: ' + textValue, objectValue); + logMessage('PubWise: ' + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logInfo(textValue, objectValue) { - utils.logInfo('PubWise: ' + textValue, objectValue); + logInfo('PubWise: ' + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logWarn(textValue, objectValue) { - utils.logWarn('PubWise: ' + textValue, objectValue); + logWarn('PubWise: ' + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logError(textValue, objectValue) { - utils.logError('PubWise: ' + textValue, objectValue); + logError('PubWise: ' + textValue, objectValue); } // function _decorateLog() { diff --git a/modules/pubxBidAdapter.js b/modules/pubxBidAdapter.js index 35d75e96f95..18d2bb11404 100644 --- a/modules/pubxBidAdapter.js +++ b/modules/pubxBidAdapter.js @@ -1,4 +1,6 @@ +import { deepSetValue } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; + const BIDDER_CODE = 'pubx'; const BID_ENDPOINT = 'https://api.primecaster.net/adlogue/api/slot/bid'; const USER_SYNC_URL = 'https://api.primecaster.net/primecaster_dmppv.html' @@ -40,6 +42,9 @@ export const spec = { ttl: body.TTL, ad: body.adm }; + if (body.adomains) { + deepSetValue(bidResponse, 'meta.advertiserDomains', Array.isArray(body.adomains) ? body.adomains : [body.adomains]); + } bidResponses.push(bidResponse); } else {}; return bidResponses; diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 136b328d381..669bd062206 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -1,8 +1,8 @@ +import { deepAccess, getGptSlotInfoForAdUnitCode, parseSizesInput, getWindowLocation, buildUrl } 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 * as utils from '../src/utils.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -21,6 +21,14 @@ let events = { deviceDetail: {} }; +function getStorage() { + try { + return window.top['sessionStorage']; + } catch (e) { + return null; + } +} + var pubxaiAnalyticsAdapter = Object.assign(adapter( { emptyUrl, @@ -34,7 +42,7 @@ var pubxaiAnalyticsAdapter = Object.assign(adapter( events.auctionInit = args; events.floorDetail = {}; events.bids = []; - const floorData = utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData'); + const floorData = deepAccess(args, 'bidderRequests.0.bids.0.floorData'); if (typeof floorData !== 'undefined') { Object.assign(events.floorDetail, floorData); } @@ -57,6 +65,7 @@ function mapBidResponse(bidResponse, status) { if (typeof bidResponse !== 'undefined') { let bid = { adUnitCode: bidResponse.adUnitCode, + gptSlotCode: getGptSlotInfoForAdUnitCode(bidResponse.adUnitCode).gptSlot || null, auctionId: bidResponse.auctionId, bidderCode: bidResponse.bidder, cpm: bidResponse.cpm, @@ -76,7 +85,7 @@ function mapBidResponse(bidResponse, status) { Object.assign(bid, { bidId: status === 'timeout' ? bidResponse.bidId : bidResponse.requestId, renderStatus: status === 'timeout' ? 3 : 2, - sizes: utils.parseSizesInput(bidResponse.size).toString(), + sizes: parseSizesInput(bidResponse.size).toString(), }); events.bids.push(bid); } else { @@ -84,7 +93,7 @@ function mapBidResponse(bidResponse, status) { bidId: bidResponse.requestId, floorProvider: events.floorDetail ? events.floorDetail.floorProvider : null, isWinningBid: true, - placementId: bidResponse.params ? utils.deepAccess(bidResponse, 'params.0.placementId') : null, + placementId: bidResponse.params ? deepAccess(bidResponse, 'params.0.placementId') : null, renderedSize: bidResponse.size, renderStatus: 4 }); @@ -130,17 +139,26 @@ pubxaiAnalyticsAdapter.shouldFireEventRequest = function (samplingRate = 1) { function send(data, status) { if (pubxaiAnalyticsAdapter.shouldFireEventRequest(initOptions.samplingRate)) { - let location = utils.getWindowLocation(); + let location = getWindowLocation(); + const storage = getStorage(); data.initOptions = initOptions; + data.pageDetail = {}; + Object.assign(data.pageDetail, { + host: location.host, + path: location.pathname, + search: location.search + }); if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { - Object.assign(data.pageDetail, { - host: location.host, - path: location.pathname, - search: location.search, - adUnitCount: data.auctionInit.adUnitCodes ? data.auctionInit.adUnitCodes.length : null - }); + data.pageDetail.adUnitCount = data.auctionInit.adUnitCodes ? data.auctionInit.adUnitCodes.length : null; data.initOptions.auctionId = data.auctionInit.auctionId; delete data.auctionInit; + + data.pmcDetail = {} + Object.assign(data.pmcDetail, { + bidDensity: storage ? storage.getItem('pbx:dpbid') : null, + maxBid: storage ? storage.getItem('pbx:mxbid') : null, + auctionId: storage ? storage.getItem('pbx:aucid') : null, + }); } data.deviceDetail = {}; Object.assign(data.deviceDetail, { @@ -149,7 +167,8 @@ function send(data, status) { deviceOS: getOS(), browser: getBrowser() }); - let pubxaiAnalyticsRequestUrl = utils.buildUrl({ + + let pubxaiAnalyticsRequestUrl = buildUrl({ protocol: 'https', hostname: (initOptions && initOptions.hostName) || defaultHost, pathname: status == 'bidwon' ? winningBidPath : auctionPath, diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index f74d79a3dc5..7aa3ad6088c 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -1,5 +1,5 @@ /* eslint dot-notation:0, quote-props:0 */ -import * as utils from '../src/utils.js'; +import { convertTypes, deepAccess, isArray, logError, isFn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; @@ -77,7 +77,7 @@ export const spec = { } }, transformBidParams: function(params, isOpenRtb) { - return utils.convertTypes({ + return convertTypes({ 'cf': 'string', 'cp': 'number', 'ct': 'number' @@ -119,12 +119,13 @@ function bidResponseAvailable(request, response) { adId: id, ttl: idToBidMap[id].exp || DEFAULT_BID_TTL, netRevenue: DEFAULT_NET_REVENUE, - currency: bidResponse.cur || DEFAULT_CURRENCY + currency: bidResponse.cur || DEFAULT_CURRENCY, + meta: { advertiserDomains: idToBidMap[id].adomain || [] } }; if (idToImpMap[id].video) { // for outstream, a renderer is specified - if (idToSlotConfig[id] && utils.deepAccess(idToSlotConfig[id], 'mediaTypes.video.context') === 'outstream') { - bid.renderer = outstreamRenderer(utils.deepAccess(idToSlotConfig[id], 'renderer.options'), utils.deepAccess(idToBidMap[id], 'ext.outstream')); + if (idToSlotConfig[id] && deepAccess(idToSlotConfig[id], 'mediaTypes.video.context') === 'outstream') { + bid.renderer = outstreamRenderer(deepAccess(idToSlotConfig[id], 'renderer.options'), deepAccess(idToBidMap[id], 'ext.outstream')); } bid.vastXml = idToBidMap[id].adm; bid.mediaType = 'video'; @@ -154,7 +155,7 @@ function impression(slot) { 'native': nativeImpression(slot), tagid: slot.params.ct.toString(), video: video(slot), - bidfloor: slot.params.bidfloor, + bidfloor: bidFloor(slot), ext: ext(slot), }; } @@ -177,9 +178,9 @@ function banner(slot) { * Produce openrtb format objects based on the sizes configured for the slot. */ function parseSizes(slot) { - const sizes = utils.deepAccess(slot, 'mediaTypes.banner.sizes'); - if (sizes && utils.isArray(sizes)) { - return sizes.filter(sz => utils.isArray(sz) && sz.length === 2).map(sz => ({ + const sizes = deepAccess(slot, 'mediaTypes.banner.sizes'); + if (sizes && isArray(sizes)) { + return sizes.filter(sz => isArray(sz) && sz.length === 2).map(sz => ({ w: sz[0], h: sz[1] })); @@ -192,7 +193,11 @@ function parseSizes(slot) { */ function video(slot) { if (slot.params.video) { - return Object.assign({}, slot.params.video, {battr: slot.params.battr}); + return Object.assign({}, + slot.params.video, // previously supported as bidder param + slot.mediaTypes && slot.mediaTypes.video ? slot.mediaTypes.video : {}, // params on mediaTypes.video + {battr: slot.params.battr} + ); } return null; } @@ -382,7 +387,7 @@ function parse(rawResponse) { return JSON.parse(rawResponse); } } catch (ex) { - utils.logError('pulsepointLite.safeParse', 'ERROR', ex); + logError('pulsepointLite.safeParse', 'ERROR', ex); } return null; } @@ -416,12 +421,18 @@ function user(bidRequest, bidderRequest) { if (bidRequest) { if (bidRequest.userId) { ext.eids = []; - addExternalUserId(ext.eids, bidRequest.userId.pubcid, 'pubcommon'); + addExternalUserId(ext.eids, bidRequest.userId.pubcid, 'pubcid.org'); addExternalUserId(ext.eids, bidRequest.userId.britepoolid, 'britepool.com'); - addExternalUserId(ext.eids, bidRequest.userId.criteoId, 'criteo'); - addExternalUserId(ext.eids, bidRequest.userId.idl_env, 'identityLink'); - addExternalUserId(ext.eids, utils.deepAccess(bidRequest, 'userId.id5id.uid'), 'id5-sync.com', utils.deepAccess(bidRequest, 'userId.id5id.ext')); - addExternalUserId(ext.eids, utils.deepAccess(bidRequest, 'userId.parrableId.eid'), 'parrable.com'); + addExternalUserId(ext.eids, bidRequest.userId.criteoId, 'criteo.com'); + addExternalUserId(ext.eids, bidRequest.userId.idl_env, 'liveramp.com'); + addExternalUserId(ext.eids, deepAccess(bidRequest, 'userId.id5id.uid'), 'id5-sync.com', deepAccess(bidRequest, 'userId.id5id.ext')); + addExternalUserId(ext.eids, deepAccess(bidRequest, 'userId.parrableId.eid'), 'parrable.com'); + addExternalUserId(ext.eids, bidRequest.userId.fabrickId, 'neustar.biz'); + addExternalUserId(ext.eids, deepAccess(bidRequest, 'userId.haloId.haloId'), 'audigent.com'); + addExternalUserId(ext.eids, bidRequest.userId.merkleId, 'merkleinc.com'); + addExternalUserId(ext.eids, bidRequest.userId.lotamePanoramaId, 'crwdcntrl.net'); + addExternalUserId(ext.eids, bidRequest.userId.connectid, 'verizonmedia.com'); + addExternalUserId(ext.eids, deepAccess(bidRequest, 'userId.uid2.id'), 'uidapi.com'); // liveintent if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { addExternalUserId(ext.eids, bidRequest.userId.lipb.lipbid, 'liveintent.com'); @@ -519,4 +530,19 @@ function nativeResponse(imp, bid) { return null; } +function bidFloor(slot) { + let floor = slot.params.bidfloor; + if (isFn(slot.getFloor)) { + const floorData = slot.getFloor({ + mediaType: slot.mediaTypes.banner ? 'banner' : slot.mediaTypes.video ? 'video' : 'Native', + size: '*', + currency: DEFAULT_CURRENCY, + }); + if (floorData && floorData.floor) { + floor = floorData.floor; + } + } + return floor; +} + registerBidder(spec); diff --git a/modules/pulsepointBidAdapter.md b/modules/pulsepointBidAdapter.md index 36d4ef6cce5..7f4b7e6b611 100644 --- a/modules/pulsepointBidAdapter.md +++ b/modules/pulsepointBidAdapter.md @@ -45,23 +45,21 @@ Please use ```pulsepoint``` as the bidder code. mediaTypes: { video: { playerSize: [640, 480], - context: 'outstream' + context: 'outstream', + h: 300, + w: 400, + minduration: 1, + maxduration: 210, + linearity: 1, + mimes: ["video/mp4", "video/ogg", "video/webm"], + pos: 3 } }, bids: [{ bidder: 'pulsepoint', params: { cp: 512379, - ct: 505642, - video: { - h: 300, - w: 400, - minduration: 1, - maxduration: 210, - linearity: 1, - mimes: ["video/mp4", "video/ogg", "video/webm"], - pos: 3 - } + ct: 505642 } }], renderer: { @@ -74,7 +72,12 @@ Please use ```pulsepoint``` as the bidder code. mediaTypes: { video: { playerSize: [640, 480], - context: 'instream' + context: 'instream', + h: 300, + w: 400, + minduration: 1, + maxduration: 210, + protocols: [2,3,5] } }, bids: [{ @@ -82,14 +85,6 @@ Please use ```pulsepoint``` as the bidder code. params: { cp: 512379, ct: 694973, - video: { - battr: [1,3], - h: 300, - w: 400, - minduration: 1, - maxduration: 210, - protocols: [2,3,5] - } } }] }]; diff --git a/modules/pxyzBidAdapter.js b/modules/pxyzBidAdapter.js index bd2189ccc39..e144eb84a01 100644 --- a/modules/pxyzBidAdapter.js +++ b/modules/pxyzBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; +import { logInfo, logError, isArray } from '../src/utils.js'; const BIDDER_CODE = 'pxyz'; const URL = 'https://ads.playground.xyz/host-config/prebid?v=2'; @@ -61,15 +61,15 @@ export const spec = { if (bidderRequest && bidderRequest.gdprConsent) { const gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; const consentString = bidderRequest.gdprConsent.consentString; - utils.logInfo(`PXYZ: GDPR applies ${gdpr}`); - utils.logInfo(`PXYZ: GDPR consent string ${consentString}`); + logInfo(`PXYZ: GDPR applies ${gdpr}`); + logInfo(`PXYZ: GDPR consent string ${consentString}`); payload.Regs.ext.gdpr = gdpr; payload.User = { ext: { consent: consentString } }; } // CCPA if (bidderRequest && bidderRequest.uspConsent) { - utils.logInfo(`PXYZ: USP Consent ${bidderRequest.uspConsent}`); + logInfo(`PXYZ: USP Consent ${bidderRequest.uspConsent}`); payload.Regs.ext['us_privacy'] = bidderRequest.uspConsent; } @@ -95,14 +95,14 @@ export const spec = { let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; - utils.logError(errorMessage); + logError(errorMessage); } return bids; } - if (!utils.isArray(serverResponse.seatbid)) { + if (!isArray(serverResponse.seatbid)) { let errorMessage = `in response for ${bidderRequest.bidderCode} adapter `; - utils.logError(errorMessage += 'Malformed seatbid response'); + logError(errorMessage += 'Malformed seatbid response'); return bids; } @@ -133,6 +133,7 @@ export const spec = { } function newBid(bid, currency) { + const { adomain } = bid; return { requestId: bid.impid, mediaType: BANNER, @@ -144,6 +145,9 @@ function newBid(bid, currency) { ttl: 300, netRevenue: true, currency: currency, + meta: { + ...(adomain && adomain.length > 0 ? { advertiserDomains: adomain } : {}) + } }; } diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index e9541edb534..e168339426d 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { deepAccess, logInfo, logError, isEmpty, isArray } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -24,33 +24,33 @@ export const QUANTCAST_FPA = '__qca'; export const storage = getStorageManager(QUANTCAST_VENDOR_ID, BIDDER_CODE); function makeVideoImp(bid) { - const video = {}; - if (bid.params.video) { - video['mimes'] = bid.params.video.mimes; - video['minduration'] = bid.params.video.minduration; - video['maxduration'] = bid.params.video.maxduration; - video['protocols'] = bid.params.video.protocols; - video['startdelay'] = bid.params.video.startdelay; - video['linearity'] = bid.params.video.linearity; - video['battr'] = bid.params.video.battr; - video['maxbitrate'] = bid.params.video.maxbitrate; - video['playbackmethod'] = bid.params.video.playbackmethod; - video['delivery'] = bid.params.video.delivery; - video['placement'] = bid.params.video.placement; - video['api'] = bid.params.video.api; - } - if (bid.mediaTypes.video.mimes) { - video['mimes'] = bid.mediaTypes.video.mimes; + const videoInMediaType = deepAccess(bid, 'mediaTypes.video') || {}; + const videoInParams = deepAccess(bid, 'params.video') || {}; + const video = Object.assign({}, videoInParams, videoInMediaType); + + if (video.playerSize) { + video.w = video.playerSize[0]; + video.h = video.playerSize[1]; } - if (utils.isArray(bid.mediaTypes.video.playerSize[0])) { - video['w'] = bid.mediaTypes.video.playerSize[0][0]; - video['h'] = bid.mediaTypes.video.playerSize[0][1]; - } else { - video['w'] = bid.mediaTypes.video.playerSize[0]; - video['h'] = bid.mediaTypes.video.playerSize[1]; + const videoCopy = { + mimes: video.mimes, + minduration: video.minduration, + maxduration: video.maxduration, + protocols: video.protocols, + startdelay: video.startdelay, + linearity: video.linearity, + battr: video.battr, + maxbitrate: video.maxbitrate, + playbackmethod: video.playbackmethod, + delivery: video.delivery, + placement: video.placement, + api: video.api, + w: video.w, + h: video.h } + return { - video: video, + video: videoCopy, placementCode: bid.placementCode, bidFloor: bid.params.bidFloor || DEFAULT_BID_FLOOR }; @@ -142,10 +142,10 @@ export const spec = { */ buildRequests(bidRequests, bidderRequest) { const bids = bidRequests || []; - const gdprConsent = utils.deepAccess(bidderRequest, 'gdprConsent') || {}; - const uspConsent = utils.deepAccess(bidderRequest, 'uspConsent'); - const referrer = utils.deepAccess(bidderRequest, 'refererInfo.referer'); - const page = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); + const gdprConsent = deepAccess(bidderRequest, 'gdprConsent') || {}; + const uspConsent = deepAccess(bidderRequest, 'uspConsent'); + const referrer = deepAccess(bidderRequest, 'refererInfo.referer'); + const page = deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || deepAccess(window, 'location.href'); const domain = getDomain(page); // Check for GDPR consent for purpose 1, and drop request if consent has not been given @@ -153,11 +153,11 @@ export const spec = { if (gdprConsent.gdprApplies) { if (gdprConsent.vendorData) { if (gdprConsent.apiVersion === 1 && !checkTCFv1(gdprConsent.vendorData)) { - utils.logInfo(`${BIDDER_CODE}: No purpose 1 consent for TCF v1`); + logInfo(`${BIDDER_CODE}: No purpose 1 consent for TCF v1`); return; } if (gdprConsent.apiVersion === 2 && !checkTCFv2(gdprConsent.vendorData)) { - utils.logInfo(`${BIDDER_CODE}: No purpose 1 consent for TCF v2`); + logInfo(`${BIDDER_CODE}: No purpose 1 consent for TCF v2`); return; } } @@ -174,7 +174,7 @@ export const spec = { imp = makeBannerImp(bid); } else { // Unsupported mediaType - utils.logInfo(`${BIDDER_CODE}: No supported mediaTypes found in ${JSON.stringify(bid.mediaTypes)}`); + logInfo(`${BIDDER_CODE}: No supported mediaTypes found in ${JSON.stringify(bid.mediaTypes)}`); return; } } else { @@ -231,24 +231,24 @@ export const spec = { */ interpretResponse(serverResponse) { if (serverResponse === undefined) { - utils.logError('Server Response is undefined'); + logError('Server Response is undefined'); return []; } const response = serverResponse['body']; if (response === undefined || !response.hasOwnProperty('bids')) { - utils.logError('Sub-optimal JSON received from Quantcast server'); + logError('Sub-optimal JSON received from Quantcast server'); return []; } - if (utils.isEmpty(response.bids)) { + if (isEmpty(response.bids)) { // Shortcut response handling if no bids are present return []; } const bidResponsesList = response.bids.map(bid => { - const { ad, cpm, width, height, creativeId, currency, videoUrl, dealId } = bid; + const { ad, cpm, width, height, creativeId, currency, videoUrl, dealId, meta } = bid; const result = { requestId: response.requestId, @@ -271,6 +271,11 @@ export const spec = { result['dealId'] = dealId; } + if (meta !== undefined && meta.advertiserDomains && isArray(meta.advertiserDomains)) { + result.meta = {}; + result.meta.advertiserDomains = meta.advertiserDomains; + } + return result; }); @@ -284,11 +289,11 @@ export const spec = { const syncs = [] if (!hasUserSynced && syncOptions.pixelEnabled) { const responseWithUrl = find(serverResponses, serverResponse => - utils.deepAccess(serverResponse.body, 'userSync.url') + deepAccess(serverResponse.body, 'userSync.url') ); if (responseWithUrl) { - const url = utils.deepAccess(responseWithUrl.body, 'userSync.url') + const url = deepAccess(responseWithUrl.body, 'userSync.url') syncs.push({ type: 'image', url: url diff --git a/modules/quantcastIdSystem.js b/modules/quantcastIdSystem.js index e86c130dc5b..7d82be884da 100644 --- a/modules/quantcastIdSystem.js +++ b/modules/quantcastIdSystem.js @@ -7,11 +7,159 @@ import {submodule} from '../src/hook.js' import { getStorageManager } from '../src/storageManager.js'; +import { triggerPixel, logInfo } from '../src/utils.js'; +import { uspDataHandler, coppaDataHandler, gdprDataHandler } from '../src/adapterManager.js'; const QUANTCAST_FPA = '__qca'; +const DEFAULT_COOKIE_EXP_DAYS = 392; // (13 months - 2 days) +const DAY_MS = 86400000; +const PREBID_PCODE = 'p-KceJUEvXN48CE'; +const QSERVE_URL = 'https://pixel.quantserve.com/pixel'; +const QUANTCAST_VENDOR_ID = '11'; +const PURPOSE_DATA_COLLECT = '1'; +const PURPOSE_PRODUCT_IMPROVEMENT = '10'; +const QC_TCF_REQUIRED_PURPOSES = [PURPOSE_DATA_COLLECT, PURPOSE_PRODUCT_IMPROVEMENT]; +const QC_TCF_CONSENT_FIRST_PURPOSES = [PURPOSE_DATA_COLLECT]; +const QC_TCF_CONSENT_ONLY_PUPROSES = [PURPOSE_DATA_COLLECT]; +const GDPR_PRIVACY_STRING = gdprDataHandler.getConsentData(); +const US_PRIVACY_STRING = uspDataHandler.getConsentData(); export const storage = getStorageManager(); +export function firePixel(clientId, cookieExpDays = DEFAULT_COOKIE_EXP_DAYS) { + // check for presence of Quantcast Measure tag _qevent obj and publisher provided clientID + if (!window._qevents && clientId && clientId != '') { + var fpa = storage.getCookie(QUANTCAST_FPA); + var fpan = '0'; + var domain = quantcastIdSubmodule.findRootDomain(); + var now = new Date(); + var usPrivacyParamString = ''; + var firstPartyParamStrings; + var gdprParamStrings; + + if (!fpa) { + var et = now.getTime(); + var expires = new Date(et + (cookieExpDays * DAY_MS)).toGMTString(); + var rand = Math.round(Math.random() * 2147483647); + fpa = `B0-${rand}-${et}`; + fpan = '1'; + storage.setCookie(QUANTCAST_FPA, fpa, expires, '/', domain, null); + } + + firstPartyParamStrings = `&fpan=${fpan}&fpa=${fpa}`; + gdprParamStrings = '&gdpr=0'; + if (GDPR_PRIVACY_STRING && typeof GDPR_PRIVACY_STRING.gdprApplies === 'boolean' && GDPR_PRIVACY_STRING.gdprApplies) { + gdprParamStrings = `gdpr=1&gdpr_consent=${GDPR_PRIVACY_STRING.consentString}`; + } + if (US_PRIVACY_STRING && typeof US_PRIVACY_STRING === 'string') { + usPrivacyParamString = `&us_privacy=${US_PRIVACY_STRING}`; + } + + let url = QSERVE_URL + + '?d=' + domain + + '&client_id=' + clientId + + '&a=' + PREBID_PCODE + + usPrivacyParamString + + gdprParamStrings + + firstPartyParamStrings; + + triggerPixel(url); + } +}; + +export function hasGDPRConsent(gdprConsent) { + // Check for GDPR consent for purpose 1 and 10, and drop request if consent has not been given + // Remaining consent checks are performed server-side. + if (gdprConsent && typeof gdprConsent.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { + if (!gdprConsent.vendorData) { + return false; + } + if (gdprConsent.apiVersion === 1) { + // We are not supporting TCF v1 + return false; + } + if (gdprConsent.apiVersion === 2) { + return checkTCFv2(gdprConsent.vendorData); + } + } + return true; +} + +export function checkTCFv2(vendorData, requiredPurposes = QC_TCF_REQUIRED_PURPOSES) { + var gdprApplies = vendorData.gdprApplies; + var purposes = vendorData.purpose; + var vendors = vendorData.vendor; + var qcConsent = vendors && vendors.consents && vendors.consents[QUANTCAST_VENDOR_ID]; + var qcInterest = vendors && vendors.legitimateInterests && vendors.legitimateInterests[QUANTCAST_VENDOR_ID]; + var restrictions = vendorData.publisher ? vendorData.publisher.restrictions : {}; + + if (!gdprApplies) { + return true; + } + + return requiredPurposes.map(function(purpose) { + var purposeConsent = purposes.consents ? purposes.consents[purpose] : false; + var purposeInterest = purposes.legitimateInterests ? purposes.legitimateInterests[purpose] : false; + + var qcRestriction = restrictions && restrictions[purpose] + ? restrictions[purpose][QUANTCAST_VENDOR_ID] + : null; + + if (qcRestriction === 0) { + return false; + } + + // Seek consent or legitimate interest based on our default legal + // basis for the purpose, falling back to the other if possible. + if ( + // we have positive vendor consent + qcConsent && + // there is positive purpose consent + purposeConsent && + // publisher does not require legitimate interest + qcRestriction !== 2 && + // purpose is a consent-first purpose or publisher has explicitly restricted to consent + (QC_TCF_CONSENT_FIRST_PURPOSES.indexOf(purpose) != -1 || qcRestriction === 1) + ) { + return true; + } else if ( + // publisher does not require consent + qcRestriction !== 1 && + // we have legitimate interest for vendor + qcInterest && + // there is legitimate interest for purpose + purposeInterest && + // purpose's legal basis does not require consent + QC_TCF_CONSENT_ONLY_PUPROSES.indexOf(purpose) == -1 && + // purpose is a legitimate-interest-first purpose or publisher has explicitly restricted to legitimate interest + (QC_TCF_CONSENT_FIRST_PURPOSES.indexOf(purpose) == -1 || qcRestriction === 2) + ) { + return true; + } + + return false; + }).reduce(function(a, b) { + return a && b; + }, true); +} + +/** + * tests if us_privacy consent string is present, us_privacy applies, and notice_given / do-not-sell is set to yes + * @returns {boolean} + */ +export function hasCCPAConsent(usPrivacyConsent) { + if ( + usPrivacyConsent && + typeof usPrivacyConsent === 'string' && + usPrivacyConsent.length == 4 && + usPrivacyConsent.charAt(1) == 'Y' && + usPrivacyConsent.charAt(2) == 'Y' + ) { + return false + } + return true; +} + /** @type {Submodule} */ export const quantcastIdSubmodule = { /** @@ -20,6 +168,12 @@ export const quantcastIdSubmodule = { */ name: 'quantcastId', + /** + * Vendor id of Quantcast + * @type {Number} + */ + gvlid: QUANTCAST_VENDOR_ID, + /** * decode the stored id value for passing to bid requests * @function @@ -34,9 +188,35 @@ export const quantcastIdSubmodule = { * @function * @returns {{id: {quantcastId: string} | undefined}}} */ - getId() { + getId(config) { // Consent signals are currently checked on the server side. let fpa = storage.getCookie(QUANTCAST_FPA); + + const coppa = coppaDataHandler.getCoppa(); + + if (coppa || !hasCCPAConsent(US_PRIVACY_STRING) || !hasGDPRConsent(GDPR_PRIVACY_STRING)) { + var expired = new Date(0).toUTCString(); + var domain = quantcastIdSubmodule.findRootDomain(); + logInfo('QuantcastId: Necessary consent not present for Id, exiting QuantcastId'); + storage.setCookie(QUANTCAST_FPA, '', expired, '/', domain, null); + return undefined; + } + + const configParams = (config && config.params) || {}; + const storageParams = (config && config.storage) || {}; + + var clientId = configParams.clientId || ''; + var cookieExpDays = storageParams.expires || DEFAULT_COOKIE_EXP_DAYS; + + // Callbacks on Event Listeners won't trigger if the event is already complete so this check is required + if (document.readyState === 'complete') { + firePixel(clientId, cookieExpDays); + } else { + window.addEventListener('load', function () { + firePixel(clientId, cookieExpDays); + }); + } + return { id: fpa ? { quantcastId: fpa } : undefined } } }; diff --git a/modules/quantcastIdSystem.md b/modules/quantcastIdSystem.md new file mode 100644 index 00000000000..cf76099e4a5 --- /dev/null +++ b/modules/quantcastIdSystem.md @@ -0,0 +1,46 @@ +#### Overview + +``` +Module Name: Quantcast Id System +Module Type: Id System +Maintainer: asig@quantcast.com +``` + +#### Description + + The Prebid Quantcast ID module stores a Quantcast ID in a first party cookie. The ID is then made available in the bid request. The ID from the cookie added in the bidstream allows Quantcast to more accurately bid on publisher inventories without third party cookies, which can result in better monetization across publisher sites from Quantcast. And, it’s free to use! For easier integration, you can work with one of our SSP partners, like PubMatic, who can facilitate the legal process as well as the software integration for you. + + Add it to your Prebid.js package with: + + `gulp build --modules=userId,quantcastIdSystem` + + Quantcast’s privacy policies for the services rendered can be found at + https://www.quantcast.com/privacy/ + + Publishers deploying the module are responsible for ensuring legally required notices and choices for users. + + The Quantcast ID module will only perform any action and return an ID in situations where: + 1. the publisher has not set a ‘coppa' flag on the prebid configuration on their site (see [pbjs.setConfig.coppa](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-coppa)) + 2. there is not a IAB us-privacy string indicating the digital property has provided user notice and the user has made a choice to opt out of sale + 3. if GDPR applies, an IAB TCF v2 string exists indicating that Quantcast does not have consent for purpose 1 (cookies, device identifiers, or other information can be stored or accessed on your device for the purposes presented to you), or an established legal basis (by default legitimate interest) for purpose 10 (your data can be used to improve existing systems and software, and to develop new products). + + #### Quantcast ID Configuration + + | Param under userSync.userIds[] | Scope | Type | Description | Example | + | --- | --- | --- | --- | --- | + | name | Required | String | `"quantcastId"` | `"quantcastId"` | + | params | Optional | Object | Details for Quantcast initialization. | | + | params.ClientID | Optional | String | Optional parameter for Quantcast prebid managed service partners. The parameter is not required for websites with Quantcast Measure tag. Reach out to Quantcast for ClientID if you are not an existing Quantcast prebid managed service partner: quantcast-idsupport@quantcast.com. | | + + + #### Quantcast ID Example + +```js + pbjs.setConfig({ + userSync: { + userIds: [{ + name: "quantcastId" + }] + } + }); +``` diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index d19ad0b4fe4..c9a86f73910 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -20,14 +20,16 @@ export const spec = { bids.push({ bidId: bidRequest.bidId, zoneToken: bidRequest.params.zoneToken, - pos: bidRequest.params.pos + pos: bidRequest.params.pos, + sizes: prepareSizes(bidRequest.sizes) }) }) let payload = { requestId: bidderRequest.bidderRequestId, bids, - referer: bidderRequest.refererInfo.referer + referer: bidderRequest.refererInfo.referer, + schain: validBidRequests[0].schain } if (bidderRequest && bidderRequest.gdprConsent) { @@ -73,6 +75,9 @@ export const spec = { bid.vastXml = bid.ad; } + bid.meta = {}; + bid.meta.advertiserDomains = bid.adomain || []; + bids.push(bid); }) @@ -90,4 +95,8 @@ export const spec = { } } +function prepareSizes(sizes) { + return sizes && sizes.map(size => ({ width: size[0], height: size[1] })); +} + registerBidder(spec); diff --git a/modules/radsBidAdapter.js b/modules/radsBidAdapter.js index 8f3cbe02f23..fee5daa3fb4 100644 --- a/modules/radsBidAdapter.js +++ b/modules/radsBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { deepAccess } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -7,10 +7,12 @@ const BIDDER_CODE = 'rads'; const ENDPOINT_URL = 'https://rads.recognified.net/md.request.php'; const ENDPOINT_URL_DEV = 'https://dcradn1.online-solution.biz/md.request.php'; const DEFAULT_VAST_FORMAT = 'vast2'; +const GVLID = 602; export const spec = { code: BIDDER_CODE, - aliases: ['rads'], + gvlid: GVLID, + aliases: [], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { return !!(bid.params.placement); @@ -18,9 +20,6 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { const params = bidRequest.params; - const videoData = utils.deepAccess(bidRequest, 'mediaTypes.video') || {}; - const sizes = utils.parseSizesInput(videoData.playerSize || bidRequest.sizes)[0]; - const [width, height] = sizes.split('x'); const placementId = params.placement; const rnd = Math.floor(Math.random() * 99999999999); @@ -30,34 +29,37 @@ export const spec = { let endpoint = isDev ? ENDPOINT_URL_DEV : ENDPOINT_URL; - let payload = {}; - if (isVideoRequest(bidRequest)) { - let vastFormat = params.vastFormat || DEFAULT_VAST_FORMAT; - payload = { - rt: vastFormat, - _f: 'prebid_js', - _ps: placementId, - srw: width, - srh: height, - idt: 100, - rnd: rnd, - p: referrer, - bid_id: bidId, - }; + let payload = { + _f: 'prebid_js', + _ps: placementId, + idt: 100, + rnd: rnd, + p: referrer, + bid_id: bidId, + }; + + let sizes; + if (isBannerRequest(bidRequest)) { + sizes = getBannerSizes(bidRequest); + payload.rt = 'bid-response'; + payload.srw = sizes[0].width; + payload.srh = sizes[0].height; } else { - payload = { - rt: 'bid-response', - _f: 'prebid_js', - _ps: placementId, - srw: width, - srh: height, - idt: 100, - rnd: rnd, - p: referrer, - bid_id: bidId, - }; + let vastFormat = params.vastFormat || DEFAULT_VAST_FORMAT; + sizes = getVideoSizes(bidRequest); + payload.rt = vastFormat; + payload.srw = sizes[0].width; + payload.srh = sizes[0].height; } - prepareExtraParams(params, payload, bidderRequest); + + if (sizes.length > 1) { + payload.alt_ad_sizes = []; + for (let i = 1; i < sizes.length; i++) { + payload.alt_ad_sizes.push(sizes[i].width + 'x' + sizes[i].height); + } + } + + prepareExtraParams(params, payload, bidderRequest, bidRequest); return { method: 'GET', @@ -84,7 +86,10 @@ export const spec = { dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout') + ttl: config.getConfig('_bidderTimeout'), + meta: { + advertiserDomains: response.adomain || [] + } }; if (response.vastXml) { @@ -114,17 +119,19 @@ export const spec = { } } - if (syncOptions.iframeEnabled) { - serverResponses[0].body.userSync.iframeUrl.forEach((url) => syncs.push({ - type: 'iframe', - url: appendToUrl(url, gdprParams) - })); - } - if (syncOptions.pixelEnabled && serverResponses.length > 0) { - serverResponses[0].body.userSync.imageUrl.forEach((url) => syncs.push({ - type: 'image', - url: appendToUrl(url, gdprParams) - })); + if (serverResponses.length > 0 && serverResponses[0].body.userSync) { + if (syncOptions.iframeEnabled) { + serverResponses[0].body.userSync.iframeUrl.forEach((url) => syncs.push({ + type: 'iframe', + url: appendToUrl(url, gdprParams) + })); + } + if (syncOptions.pixelEnabled) { + serverResponses[0].body.userSync.imageUrl.forEach((url) => syncs.push({ + type: 'image', + url: appendToUrl(url, gdprParams) + })); + } } return syncs; } @@ -151,18 +158,15 @@ function objectToQueryString(obj, prefix) { } return str.join('&'); } - /** - * Check if it's a video bid request + * Add extra params to server request * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {boolean} True if it's a video bid + * @param params + * @param payload + * @param bidderRequest + * @param {BidRequest} bidRequest - Bid request generated from ad slots */ -function isVideoRequest(bid) { - return bid.mediaType === 'video' || !!utils.deepAccess(bid, 'mediaTypes.video'); -} - -function prepareExtraParams(params, payload, bidderRequest) { +function prepareExtraParams(params, payload, bidderRequest, bidRequest) { if (params.pfilter !== undefined) { payload.pfilter = params.pfilter; } @@ -196,6 +200,77 @@ function prepareExtraParams(params, payload, bidderRequest) { if (params.ip !== undefined) { payload.i = params.ip; } + + if (bidRequest.userId && bidRequest.userId.netId) { + payload.did_netid = bidRequest.userId.netId; + } + if (bidRequest.userId && bidRequest.userId.uid2) { + payload.did_uid2 = bidRequest.userId.uid2; + } +} + +/** + * Check if it's a banner bid request + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {boolean} True if it's a banner bid + */ +function isBannerRequest(bid) { + return bid.mediaType === 'banner' || !!deepAccess(bid, 'mediaTypes.banner') || !isVideoRequest(bid); +} + +/** + * Check if it's a video bid request + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {boolean} True if it's a video bid + */ +function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); +} + +/** + * Get video sizes + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} True if it's a video bid + */ +function getVideoSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +/** + * Get banner sizes + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} True if it's a video bid + */ +function getBannerSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +/** + * Parse size + * @param sizes + * @returns {width: number, h: height} + */ +function parseSize(size) { + let sizeObj = {} + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + return sizeObj; +} + +/** + * Parse sizes + * @param sizes + * @returns {{width: number , height: number }[]} + */ +function parseSizes(sizes) { + if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) + return sizes.map(size => parseSize(size)); + } + return [parseSize(sizes)]; // or a single one ? (ie. [728,90]) } registerBidder(spec); diff --git a/modules/radsBidAdapter.md b/modules/radsBidAdapter.md index 6e970093154..a00b82e20cb 100644 --- a/modules/radsBidAdapter.md +++ b/modules/radsBidAdapter.md @@ -25,6 +25,7 @@ RADS Bidder Adapter for Prebid.js 1.x bidder: "rads", params: { placement: 3, // placement ID + vastFormat: "vast2", // vast2(default) or vast4 devMode: true // if true: library uses dev server for tests } } diff --git a/modules/realvuAnalyticsAdapter.js b/modules/realvuAnalyticsAdapter.js index 95e62397c2c..832e907893c 100644 --- a/modules/realvuAnalyticsAdapter.js +++ b/modules/realvuAnalyticsAdapter.js @@ -3,11 +3,9 @@ import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; - +import { logMessage, logError } from '../src/utils.js'; const storage = getStorageManager(); -const utils = require('../src/utils.js'); - let realvuAnalyticsAdapter = adapter({ global: 'realvuAnalytics', handler: 'on', @@ -42,7 +40,7 @@ export let lib = { init: function () { let z = this; let u = navigator.userAgent; - z.device = u.match(/iPad|Tablet/gi) ? 'tablet' : u.match(/iPhone|iPod|Android|Opera Mini|IEMobile/gi) ? 'mobile' : 'desktop'; + z.device = u.match(/iPhone|iPod|Android|Opera Mini|IEMobile/gi) ? 'mobile' : 'desktop'; if (typeof (z.len) == 'undefined') z.len = 0; z.ie = navigator.appVersion.match(/MSIE/); z.saf = (u.match(/Safari/) && !u.match(/Chrome/)); @@ -306,28 +304,46 @@ export let lib = { if (!restored) { a.target = z.questA(a.div); let target = (a.target !== null) ? a.target : a.div; - a.box.w = Math.max(target.offsetWidth, a.w); - a.box.h = Math.max(target.offsetHeight, a.h); - let q = z.findPosG(target); - let pad = {}; - pad.t = z.padd(target, 'Top'); - pad.l = z.padd(target, 'Left'); - pad.r = z.padd(target, 'Right'); - pad.b = z.padd(target, 'Bottom'); - let ax = q.x + pad.l; - let ay = q.y + pad.t; - a.box.x = ax; - a.box.y = ay; - if (a.box.w > a.w && a.box.w > 1) { - ax += (a.box.w - a.w - pad.l - pad.r) / 2; - } - if (a.box.h > a.h && a.box.h > 1) { - ay += (a.box.h - a.h - pad.t - pad.b) / 2; - } - if ((ax > 0 && ay > 0) && (a.x != ax || a.y != ay)) { - a.x = ax; - a.y = ay; - z.writePos(a); + if (window.getComputedStyle(target).display == 'none') { + let targSibl = target.previousElementSibling; // for 'none' containers on mobile define y as previous sibling y+h + if (targSibl) { + let q = z.findPosG(targSibl); + a.x = q.x; + a.y = q.y + targSibl.offsetHeight; + } else { + target = target.parentNode; + let q = z.findPosG(target); + a.x = q.x; + a.y = q.y; + } + a.box.x = a.x; + a.box.y = a.y; + a.box.w = a.w; + a.box.h = a.h; + } else { + a.box.w = Math.max(target.offsetWidth, a.w); + a.box.h = Math.max(target.offsetHeight, a.h); + let q = z.findPosG(target); + let pad = {}; + pad.t = z.padd(target, 'Top'); + pad.l = z.padd(target, 'Left'); + pad.r = z.padd(target, 'Right'); + pad.b = z.padd(target, 'Bottom'); + let ax = q.x + pad.l; + let ay = q.y + pad.t; + a.box.x = ax; + a.box.y = ay; + if (a.box.w > a.w && a.box.w > 1) { + ax += (a.box.w - a.w - pad.l - pad.r) / 2; + } + if (a.box.h > a.h && a.box.h > 1) { + ay += (a.box.h - a.h - pad.t - pad.b) / 2; + } + if ((ax > 0 && ay > 0) && (a.x != ax || a.y != ay)) { + a.x = ax; + a.y = ay; + z.writePos(a); + } } } let vtr = ((a.box.w * a.box.h) < 242500) ? 49 : 29; // treashfold more then 49% and more then 29% for "oversized" @@ -383,7 +399,7 @@ export let lib = { // @if NODE_ENV='debug' let now = new Date(); let msg = (now.getTime() - time0) / 1000 + ' RENDERED ' + a.unit_id; - utils.logMessage(msg); + logMessage(msg); // @endif let rpt = z.bids_rpt(a, true); z.track(a, 'rend', rpt); @@ -670,7 +686,7 @@ export let lib = { }; } let a = window.top1.realvu_aa.check(p1); - return a.r; + return a.riff; }, checkBidIn: function(partnerId, args, b) { // process a bid from hb @@ -867,7 +883,7 @@ realvuAnalyticsAdapter.originEnableAnalytics = realvuAnalyticsAdapter.enableAnal realvuAnalyticsAdapter.enableAnalytics = function (config) { _options = config.options; if (typeof (_options.partnerId) == 'undefined' || _options.partnerId == '') { - utils.logError('Missed realvu.com partnerId parameter', 101, 'Missed partnerId parameter'); + logError('Missed realvu.com partnerId parameter', 101, 'Missed partnerId parameter'); } realvuAnalyticsAdapter.originEnableAnalytics(config); return _options.partnerId; @@ -887,7 +903,7 @@ realvuAnalyticsAdapter.track = function ({eventType, args}) { ' creativei_id=' + args.creative_id; } // msg += '\nargs=' + JSON.stringify(args) + '
'; - utils.logMessage(msg); + logMessage(msg); // @endif const boost = window.top1.realvu_aa; @@ -917,7 +933,7 @@ realvuAnalyticsAdapter.track = function ({eventType, args}) { realvuAnalyticsAdapter.checkIn = function (bid, partnerId) { // find (or add if not registered yet) the unit in boost if (typeof (partnerId) == 'undefined' || partnerId == '') { - utils.logError('Missed realvu.com partnerId parameter', 102, 'Missed partnerId parameter'); + logError('Missed realvu.com partnerId parameter', 102, 'Missed partnerId parameter'); } let a = window.top1.realvu_aa.check({ unit_id: bid.adUnitCode, diff --git a/modules/reconciliationRtdProvider.js b/modules/reconciliationRtdProvider.js index f8636862d4c..fc5f0ab621a 100644 --- a/modules/reconciliationRtdProvider.js +++ b/modules/reconciliationRtdProvider.js @@ -18,7 +18,7 @@ import { submodule } from '../src/hook.js'; import { ajaxBuilder } from '../src/ajax.js'; -import * as utils from '../src/utils.js'; +import { isGptPubadsDefined, timestamp, generateUUID, logError } from '../src/utils.js'; import find from 'core-js-pure/features/array/find.js'; /** @type {Object} */ @@ -52,7 +52,7 @@ function handleAdMessage(e) { } if (data.type === MessageType.IMPRESSION_REQUEST) { - if (utils.isGptPubadsDefined()) { + if (isGptPubadsDefined()) { // 1. Find the last iframed window before window.top where the tracker was injected // (the tracker could be injected in nested iframes) const adWin = getTopIFrameWin(e.source); @@ -65,7 +65,7 @@ function handleAdMessage(e) { adDeliveryId = adSlot.getTargeting('RSDK_ADID'); adDeliveryId = adDeliveryId.length ? adDeliveryId[0] - : `${utils.timestamp()}-${utils.generateUUID()}`; + : `${timestamp()}-${generateUUID()}`; } } } @@ -136,7 +136,7 @@ export function getTopIFrameWin(win, topWin) { * @return {Object[]} slot GoogleTag slots */ function getAllSlots() { - return utils.isGptPubadsDefined() && window.googletag.pubads().getSlots(); + return isGptPubadsDefined() && window.googletag.pubads().getSlots(); } /** @@ -246,7 +246,7 @@ function getReconciliationData(adUnitsCodes) { const adSlot = getSlotByCode(adUnitCode); const adUnitId = adSlot ? adSlot.getAdUnitPath() : adUnitCode; - const adDeliveryId = `${utils.timestamp()}-${utils.generateUUID()}`; + const adDeliveryId = `${timestamp()}-${generateUUID()}`; dataToReturn[adUnitCode] = { RSDK_AUID: adUnitId, @@ -287,7 +287,7 @@ function init(moduleConfig) { _moduleParams = Object.assign({}, DEFAULT_PARAMS, params); initListeners(); } else { - utils.logError('missing params for Reconciliation provider'); + logError('missing params for Reconciliation provider'); } return true; } diff --git a/modules/reklamstoreBidAdapter.js b/modules/reklamstoreBidAdapter.js deleted file mode 100644 index 3d78cf95978..00000000000 --- a/modules/reklamstoreBidAdapter.js +++ /dev/null @@ -1,148 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'reklamstore'; -const ENDPOINT_URL = 'https://ads.rekmob.com/m/prebid'; -const CURRENCY = 'USD'; -const TIME_TO_LIVE = 360; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - /** - * 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) { - return !!(bid.params.regionId); - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function (validBidRequests, bidderRequest) { - const url = bidderRequest.refererInfo.referer; - let requests = []; - utils._each(validBidRequests, function(bid) { - requests.push({ - method: 'GET', - url: ENDPOINT_URL, - data: { - regionId: bid.params.regionId, - dt: getDeviceType(), - os: getOS(), - ref: extractDomain(url), - _: (new Date().getTime()), - mobile_web: 1 - }, - bidId: bid.bidId - }); - }); - return requests; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, bidRequest) { - try { - const bidResponse = serverResponse.body; - const bidResponses = []; - if (bidResponse) { - bidResponses.push({ - requestId: bidRequest.bidId, - cpm: parseFloat(bidResponse.cpm), - width: bidResponse.w, - height: bidResponse.h, - creativeId: bidResponse.adId || 1, - currency: CURRENCY, - netRevenue: true, - ttl: TIME_TO_LIVE, - ad: bidResponse.ad - }); - } - return bidResponses; - } catch (err) { - utils.logError(err); - return []; - } - }, - /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function(syncOptions, serverResponses) { - const syncs = []; - utils._each(serverResponses, function(bidResponse) { - utils._each(bidResponse.body.syncs, function(sync) { - if (syncOptions.pixelEnabled && sync.type == 'image') { - syncs.push({ - type: sync.type, - url: sync.url - }); - } else if (syncOptions.iframeEnabled && sync.type == 'iframe') { - syncs.push({ - type: sync.type, - url: sync.url - }); - } - }); - }); - return syncs; - } -} -registerBidder(spec); - -function getDeviceType() { - let PHONE = 0; - let TABLET = 2; - let DESKTOP = 3; - if (isPhone()) { - return PHONE; - } else if (isTablet()) { - return TABLET; - } else { - return DESKTOP; - } -} -function isPhone() { - var check = false; - (function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true })(navigator.userAgent || navigator.vendor || window.opera); - return check; -} -function isTablet() { - var check = false; - (function(a) { if (/ipad|android|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(a)) { check = true; } })(navigator.userAgent || navigator.vendor || window.opera); - return check; -} -function getOS() { - var ua = navigator.userAgent; - if (ua.match(/(iPhone|iPod|iPad)/)) { - return '1'; - } else if (ua.match(/Android/)) { - return '0'; - } else { - return '3'; - } -} -function extractDomain(url) { - var domain; - if (url.indexOf('://') > -1) { - domain = url.split('/')[2]; - } else { - domain = url.split('/')[0]; - } - domain = domain.split(':')[0]; - return domain; -} diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index c77afbe6ec5..d3e3e10c507 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { deepAccess, logWarn, getBidIdParameter, parseQueryStringParameters, triggerPixel, generateUUID, isArray } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; @@ -6,88 +6,112 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'relaido'; const BIDDER_DOMAIN = 'api.relaido.jp'; -const ADAPTER_VERSION = '1.0.2'; +const ADAPTER_VERSION = '1.0.7'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; const storage = getStorageManager(); function isBidRequestValid(bid) { - if (!utils.deepAccess(bid, 'params.placementId')) { - utils.logWarn('placementId param is reqeuired.'); + if (!deepAccess(bid, 'params.placementId')) { + logWarn('placementId param is reqeuired.'); return false; } - if (hasVideoMediaType(bid)) { - if (!isVideoValid(bid)) { - utils.logWarn('Invalid mediaType video.'); - return false; - } - } else if (hasBannerMediaType(bid)) { - if (!isBannerValid(bid)) { - utils.logWarn('Invalid mediaType banner.'); - return false; - } + if (hasVideoMediaType(bid) && isVideoValid(bid)) { + return true; } else { - utils.logWarn('Invalid mediaTypes input banner or video.'); - return false; + logWarn('Invalid mediaType video.'); } - return true; + if (hasBannerMediaType(bid) && isBannerValid(bid)) { + return true; + } else { + logWarn('Invalid mediaType banner.'); + } + return false; } function buildRequests(validBidRequests, bidderRequest) { - let bidRequests = []; + const bids = []; + let imuid = null; + let bidDomain = null; + let bidder = null; + let count = null; for (let i = 0; i < validBidRequests.length; i++) { const bidRequest = validBidRequests[i]; - const placementId = utils.getBidIdParameter('placementId', bidRequest.params); - const bidDomain = bidRequest.params.domain || BIDDER_DOMAIN; - const bidUrl = `https://${bidDomain}/bid/v1/prebid/${placementId}`; - const uuid = getUuid(); - const mediaType = getMediaType(bidRequest); - - let payload = { - version: ADAPTER_VERSION, - timeout_ms: bidderRequest.timeout, - ad_unit_code: bidRequest.adUnitCode, - auction_id: bidRequest.auctionId, - bidder: bidRequest.bidder, - bidder_request_id: bidRequest.bidderRequestId, - bid_requests_count: bidRequest.bidRequestsCount, - bid_id: bidRequest.bidId, - transaction_id: bidRequest.transactionId, - media_type: mediaType, - uuid: uuid, - }; + let mediaType = ''; + let width = 0; + let height = 0; + + if (hasVideoMediaType(bidRequest) && isVideoValid(bidRequest)) { + const playerSize = getValidSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')); + width = playerSize[0][0]; + height = playerSize[0][1]; + mediaType = VIDEO; + } else if (hasBannerMediaType(bidRequest) && isBannerValid(bidRequest)) { + const sizes = getValidSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')); + width = sizes[0][0]; + height = sizes[0][1]; + mediaType = BANNER; + } + + if (!imuid) { + const pickImuid = deepAccess(bidRequest, 'userId.imuid'); + if (pickImuid) { + imuid = pickImuid; + } + } + + if (!bidDomain) { + bidDomain = bidRequest.params.domain; + } - if (hasVideoMediaType(bidRequest)) { - const playerSize = getValidSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize')); - payload.width = playerSize[0][0]; - payload.height = playerSize[0][1]; - } else if (hasBannerMediaType(bidRequest)) { - const sizes = getValidSizes(utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes')); - payload.width = sizes[0][0]; - payload.height = sizes[0][1]; + if (!bidder) { + bidder = bidRequest.bidder } - // It may not be encoded, so add it at the end of the payload - payload.ref = bidderRequest.refererInfo.referer; - - bidRequests.push({ - method: 'GET', - url: bidUrl, - data: payload, - options: { - withCredentials: true - }, - bidId: bidRequest.bidId, + if (!bidder) { + bidder = bidRequest.bidder + } + + if (!count) { + count = bidRequest.bidRequestsCount; + } + + bids.push({ + bid_id: bidRequest.bidId, + placement_id: getBidIdParameter('placementId', bidRequest.params), + transaction_id: bidRequest.transactionId, + bidder_request_id: bidRequest.bidderRequestId, + ad_unit_code: bidRequest.adUnitCode, + auction_id: bidRequest.auctionId, player: bidRequest.params.player, - width: payload.width, - height: payload.height, - mediaType: mediaType, + width: width, + height: height, + media_type: mediaType }); } - return bidRequests; + const data = JSON.stringify({ + version: ADAPTER_VERSION, + bids: bids, + timeout_ms: bidderRequest.timeout, + bidder: bidder, + bid_requests_count: count, + uuid: getUuid(), + pv: '$prebid.version$', + imuid: imuid, + ref: bidderRequest.refererInfo.referer + }) + + return { + method: 'POST', + url: `https://${bidDomain || BIDDER_DOMAIN}/bid/v1/sprebid`, + options: { + withCredentials: true + }, + data: data + }; } function interpretResponse(serverResponse, bidRequest) { @@ -97,31 +121,39 @@ function interpretResponse(serverResponse, bidRequest) { return []; } - const playerUrl = bidRequest.player || body.playerUrl; - const mediaType = bidRequest.mediaType || VIDEO; - - let bidResponse = { - requestId: bidRequest.bidId, - width: bidRequest.width, - height: bidRequest.height, - cpm: body.price, - currency: body.currency, - creativeId: body.creativeId, - dealId: body.dealId || '', - ttl: body.ttl || DEFAULT_TTL, - netRevenue: true, - mediaType: mediaType, - }; - if (mediaType === VIDEO) { - bidResponse.vastXml = body.vast; - bidResponse.renderer = newRenderer(bidRequest.bidId, playerUrl); - } else { - const playerTag = createPlayerTag(playerUrl); - const renderTag = createRenderTag(bidRequest.width, bidRequest.height, body.vast); - bidResponse.ad = `
${playerTag}${renderTag}
`; + for (const res of body.ads) { + const playerUrl = res.playerUrl || bidRequest.player || body.playerUrl; + let bidResponse = { + requestId: res.bidId, + width: res.width, + height: res.height, + cpm: res.price, + currency: res.currency, + creativeId: res.creativeId, + playerUrl: playerUrl, + dealId: body.dealId || '', + ttl: body.ttl || DEFAULT_TTL, + netRevenue: true, + mediaType: res.mediaType || VIDEO, + meta: { + advertiserDomains: res.adomain || [], + mediaType: VIDEO + } + }; + + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastXml = res.vast; + bidResponse.renderer = newRenderer(res.bidId, playerUrl); + } else { + const playerTag = createPlayerTag(playerUrl); + const renderTag = createRenderTag(res.width, res.height, res.vast); + bidResponse.ad = `
${playerTag}${renderTag}
`; + } + bidResponses.push(bidResponse); } - bidResponses.push(bidResponse); + // eslint-disable-next-line no-console + console.log(JSON.stringify(bidResponses)); return bidResponses; } @@ -131,42 +163,43 @@ function getUserSyncs(syncOptions, serverResponses) { } let syncUrl = `https://${BIDDER_DOMAIN}/tr/v1/prebid/sync.html`; if (serverResponses.length > 0) { - syncUrl = utils.deepAccess(serverResponses, '0.body.syncUrl') || syncUrl; + syncUrl = deepAccess(serverResponses, '0.body.syncUrl') || syncUrl; } return [{ type: 'iframe', - url: syncUrl + url: `${syncUrl}?uu=${getUuid()}` }]; } function onBidWon(bid) { - let query = utils.parseQueryStringParameters({ - placement_id: utils.deepAccess(bid, 'params.0.placementId'), - creative_id: utils.deepAccess(bid, 'creativeId'), - price: utils.deepAccess(bid, 'cpm'), - auction_id: utils.deepAccess(bid, 'auctionId'), - bid_id: utils.deepAccess(bid, 'requestId'), - ad_id: utils.deepAccess(bid, 'adId'), - ad_unit_code: utils.deepAccess(bid, 'adUnitCode'), + let query = parseQueryStringParameters({ + placement_id: deepAccess(bid, 'params.0.placementId'), + creative_id: deepAccess(bid, 'creativeId'), + price: deepAccess(bid, 'cpm'), + auction_id: deepAccess(bid, 'auctionId'), + bid_id: deepAccess(bid, 'requestId'), + ad_id: deepAccess(bid, 'adId'), + ad_unit_code: deepAccess(bid, 'adUnitCode'), ref: window.location.href, }).replace(/\&$/, ''); - const bidDomain = utils.deepAccess(bid, 'params.0.domain') || BIDDER_DOMAIN; + const bidDomain = deepAccess(bid, 'params.0.domain') || BIDDER_DOMAIN; const burl = `https://${bidDomain}/tr/v1/prebid/win.gif?${query}`; - utils.triggerPixel(burl); + triggerPixel(burl); } function onTimeout(data) { - let query = utils.parseQueryStringParameters({ - placement_id: utils.deepAccess(data, '0.params.0.placementId'), - timeout: utils.deepAccess(data, '0.timeout'), - auction_id: utils.deepAccess(data, '0.auctionId'), - bid_id: utils.deepAccess(data, '0.bidId'), - ad_unit_code: utils.deepAccess(data, '0.adUnitCode'), + let query = parseQueryStringParameters({ + placement_id: deepAccess(data, '0.params.0.placementId'), + timeout: deepAccess(data, '0.timeout'), + auction_id: deepAccess(data, '0.auctionId'), + bid_id: deepAccess(data, '0.bidId'), + ad_unit_code: deepAccess(data, '0.adUnitCode'), + version: ADAPTER_VERSION, ref: window.location.href, }).replace(/\&$/, ''); - const bidDomain = utils.deepAccess(data, '0.params.0.domain') || BIDDER_DOMAIN; + const bidDomain = deepAccess(data, '0.params.0.domain') || BIDDER_DOMAIN; const timeoutUrl = `https://${bidDomain}/tr/v1/prebid/timeout.gif?${query}`; - utils.triggerPixel(timeoutUrl); + triggerPixel(timeoutUrl); } function createPlayerTag(playerUrl) { @@ -193,7 +226,7 @@ function newRenderer(bidId, playerUrl) { try { renderer.setRender(outstreamRender); } catch (err) { - utils.logWarn('renderer.setRender Error', err); + logWarn('renderer.setRender Error', err); } return renderer; } @@ -214,7 +247,7 @@ function isBannerValid(bid) { if (!isMobile()) { return false; } - const sizes = getValidSizes(utils.deepAccess(bid, 'mediaTypes.banner.sizes')); + const sizes = getValidSizes(deepAccess(bid, 'mediaTypes.banner.sizes')); if (sizes.length > 0) { return true; } @@ -222,9 +255,9 @@ function isBannerValid(bid) { } function isVideoValid(bid) { - const playerSize = getValidSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize')); + const playerSize = getValidSizes(deepAccess(bid, 'mediaTypes.video.playerSize')); if (playerSize.length > 0) { - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + const context = deepAccess(bid, 'mediaTypes.video.context'); if (context && context === 'outstream') { return true; } @@ -235,7 +268,7 @@ function isVideoValid(bid) { function getUuid() { const id = storage.getCookie(UUID_KEY) if (id) return id; - const newId = utils.generateUUID(); + const newId = generateUUID(); storage.setCookie(UUID_KEY, newId); return newId; } @@ -248,31 +281,25 @@ export function isMobile() { return false; } -function getMediaType(bid) { - if (hasVideoMediaType(bid)) { - return VIDEO; - } else if (hasBannerMediaType(bid)) { - return BANNER; - } - return ''; -} - function hasBannerMediaType(bid) { - return !!utils.deepAccess(bid, 'mediaTypes.banner'); + return !!deepAccess(bid, 'mediaTypes.banner'); } function hasVideoMediaType(bid) { - return !!utils.deepAccess(bid, 'mediaTypes.video'); + return !!deepAccess(bid, 'mediaTypes.video'); } function getValidSizes(sizes) { let result = []; - if (sizes && utils.isArray(sizes) && sizes.length > 0) { + if (sizes && isArray(sizes) && sizes.length > 0) { for (let i = 0; i < sizes.length; i++) { - if (utils.isArray(sizes[i]) && sizes[i].length == 2) { + if (isArray(sizes[i]) && sizes[i].length == 2) { const width = sizes[i][0]; const height = sizes[i][1]; - if ((width >= 300 && height >= 250) || (width == 1 && height == 1)) { + if (width == 1 && height == 1) { + return [[1, 1]]; + } + if ((width >= 300 && height >= 250)) { result.push([width, height]); } } diff --git a/modules/reloadBidAdapter.js b/modules/reloadBidAdapter.js deleted file mode 100644 index 94ea4be281f..00000000000 --- a/modules/reloadBidAdapter.js +++ /dev/null @@ -1,419 +0,0 @@ -import { - registerBidder -} - from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const storage = getStorageManager(); - -const BIDDER_CODE = 'reload'; -const VERSION_ADAPTER = '1.10'; -export const spec = { - code: BIDDER_CODE, - png: {}, - /** - * 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) { - return !!(bid.params && bid.params.plcmID && bid.params.partID && 'opdomID' in bid.params && - 'bsrvID' in bid.params && bid.params.bsrvID >= 0 && bid.params.bsrvID <= 99); - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function (validBidRequests, bidderRequest) { - let vRequests = []; - let bidReq = { - id: Math.random().toString(10).substring(2), - imp: [] - }; - let vgdprConsent = null; - if (utils.deepAccess(bidderRequest, 'gdprConsent')) { - vgdprConsent = bidderRequest.gdprConsent; - } - let vPrxClientTool = null; - let vSrvUrl = null; - for (let vIdx = 0; vIdx < validBidRequests.length; vIdx++) { - let bidRequest = validBidRequests[vIdx]; - vPrxClientTool = new ReloadClientTool({ - prxVer: VERSION_ADAPTER, - prxType: 'bd', - plcmID: bidRequest.params.plcmID, - partID: bidRequest.params.partID, - opdomID: bidRequest.params.opdomID, - bsrvID: bidRequest.params.bsrvID, - gdprObj: vgdprConsent, - mediaObj: bidRequest.mediaTypes, - wnd: utils.getWindowTop(), - rtop: utils.deepAccess(bidderRequest, 'refererInfo.reachedTop') || false - }); - if (vSrvUrl === null) vSrvUrl = vPrxClientTool.getSrvUrl(); - let vImpression = { - id: bidRequest.bidId, - bidId: bidRequest.bidId, - adUnitCode: bidRequest.adUnitCode, - transactionId: bidRequest.transactionId, - bidderRequestId: bidRequest.bidderRequestId, - auctionId: bidRequest.auctionId, - banner: { - ext: { - type: bidRequest.params.type || 'pcm', - pcmdata: vPrxClientTool.getPCMObj() - } - } - }; - bidReq.imp.push(vImpression); - } - if (bidReq.imp.length > 0) { - const payloadString = JSON.stringify(bidReq); - vRequests.push({ - method: 'POST', - url: vSrvUrl, - data: payloadString - }); - } - return vRequests; - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, bidRequest) { - const serverBody = serverResponse.body; - const bidResponses = []; - for (let vIdx = 0; vIdx < serverBody.seatbid.length; vIdx++) { - let vSeatBid = serverBody.seatbid[vIdx]; - for (let vIdxBid = 0; vIdxBid < vSeatBid.bid.length; vIdxBid++) { - let vBid = vSeatBid.bid[vIdxBid]; - let vPrxClientTool = new ReloadClientTool({ - plcmID: vBid.ext.plcmID, - partID: vBid.ext.partID, - opdomID: vBid.ext.opdomID, - bsrvID: vBid.ext.bsrvID - }); - vPrxClientTool.setPCMObj(vBid.ext.pcmdata); - if (vPrxClientTool.getBP() > 0) { - let bidResponse = { - requestId: vBid.impid, - ad: vPrxClientTool.getAM(), - cpm: vPrxClientTool.getBP() / 100, - width: vPrxClientTool.getW(), - height: vPrxClientTool.getH(), - creativeId: vBid.id, - currency: vPrxClientTool.getBC(), - ttl: 300, - netRevenue: true - }; - bidResponses.push(bidResponse); - this.png[vBid.ext.adUnitCode] = vPrxClientTool.getPingUrl('bidwon'); - } - } - } - return bidResponses; - }, - /** - * Register bidder specific code, which will execute if a bid from this bidder won the auction - * @param {Bid} The bid that won the auction - */ - onBidWon: function (bid) { - if (typeof this.png[bid.adUnitCode] !== 'string' || this.png[bid.adUnitCode] === '') return; - (new Image()).src = this.png[bid.adUnitCode]; - } -}; - -function ReloadClientTool(args) { - var that = this; - var _pcmClientVersion = '120'; - var _pcmFilePref = 'prx_root_'; - var _resFilePref = 'prx_pnws_'; - var _pcmInputObjVers = '120'; - var _instObj = null; - var _status = 'NA'; - var _message = ''; - var _log = ''; - var _memFile = _getMemFile(); - - if (_memFile.status !== 'ok') { - _log += 'WARNING: clnt-int mem file initialized\n'; - } - - that.getPCMObj = function () { - return { - thisVer: _pcmInputObjVers, - statStr: _memFile.statStr, - plcmData: _getPlcmData(), - clntData: _getClientData(args.wnd, args.rtop), - resultData: _getRD(), - gdprObj: _getGdpr(), - mediaObj: _getMediaObj(), - proxetString: null, - dboData: null, - plcmSett: null, - }; - }; - - that.setPCMObj = function (obj) { - if (obj.thisVer !== '100') { - _status = 'error'; - _message = 'incomp_output_obj_version'; - _log += ' ERROR incomp_output_obj_version'; - return; - } - - _status = obj.status; - _message = obj.message; - _log += ' ' + obj.log; - - if (obj.status !== 'ok') return; - - _saveMemFile(obj.statStr, obj.srvUrl); - _instObj = obj.instr; - }; - - that.getSrvUrl = function () { - var effSrvUrl = getBidServerUrl(0); - - if (isNaN(parseInt(args.bsrvID)) !== true) effSrvUrl = getBidServerUrl(parseInt(args.bsrvID)); - - if (typeof _memFile.srvUrl === 'string' && _memFile.srvUrl !== '') effSrvUrl = _memFile.srvUrl; - - return 'https://' + effSrvUrl + '/bid'; - - function getBidServerUrl (idx) { - return 'bidsrv' + getTwoDigitString(idx) + '.reload.net'; - - function getTwoDigitString (idx) { - if (idx >= 10) return '' + idx; - else return '0' + idx; - } - } - }; - - that.getMT = function () { - return _checkInstProp('mtype', 'dsp'); - }; - - that.getW = function () { - return _checkInstProp('width', 0); - }; - - that.getH = function () { - return _checkInstProp('height', 0); - }; - - that.getBP = function () { - return _checkInstProp('prc', 0); - }; - - that.getBC = function () { - return _checkInstProp('cur', 'USD'); - }; - - that.getAM = function () { - return _checkInstProp('am', null); - }; - - that.getPingUrl = function (pingName) { - var pingData = _checkInstProp('pingdata', {}); - if (pingData[pingName] !== 'undefined') return pingData[pingName]; - return ''; - }; - - that.setRD = function (data) { - return _setRD(data); - }; - - that.getStat = function () { - return _status; - }; - - that.getMsg = function () { - return _message; - }; - - that.getLog = function () { - return _log; - }; - - function _checkInstProp (key, def) { - if (_instObj === null) return def; - if (typeof _instObj === 'undefined') return def; - if (_instObj.go !== true) return def; - if (typeof _instObj[key] === 'undefined') return def; - return _instObj[key]; - } - - function _getPlcmData () { - return { - prxVer: args.prxVer, - prxType: args.prxType, - plcmID: args.plcmID, - partID: args.partID, - opdomID: args.opdomID, - bsrvID: args.bsrvID, - dmod: args.dmod, - lmod: args.lmod, - lplcmID: args.lplcmID, - }; - } - - function _getClientData (wnd, rtop) { - return { - version: 200, - locTime: Date.now(), - winInfo: _winInf(wnd), - envInfo: getEnvInfo(), - topw: rtop === true, - prot: wnd.document.location.protocol, - host: wnd.document.location.host, - title: wnd.document.title, - }; - - function _winInf (wnd) { - return { - phs: { - w: wnd.screen.width, - h: wnd.screen.height - }, - avl: { - w: wnd.screen.availWidth, - h: wnd.screen.availHeight - }, - inr: { - w: wnd.innerWidth, - h: wnd.innerHeight - }, - bdy: { - w: wnd.document.body.clientWidth, - h: wnd.document.body.clientHeight - } - }; - } - - function getEnvInfo() { - return { - userAgent: navigator.userAgent, - appName: navigator.appName, - appVersion: navigator.appVersion - }; - } - } - - function _getMemFile () { - try { - var memFileObj = _getItem(_getMemFileName()); - - if (memFileObj === null) throw { s: 'init' }; - - if (typeof memFileObj.statStr !== 'string') throw { s: 'error' }; - if (typeof memFileObj.srvUrl !== 'string') throw { s: 'error' }; - - memFileObj.status = 'ok'; - - return memFileObj; - } catch (err) { - var retObj = { - statStr: null, - srvUrl: null - }; - retObj.status = err.s; - - return retObj; - } - } - - function _saveMemFile (statStr, srvUrl) { - try { - var fileData = { - statStr: statStr, - srvUrl: srvUrl, - }; - _setItem(_getMemFileName(), fileData); - return true; - } catch (err) { - return false; - } - } - - function _getMemFileName () { - return _pcmFilePref + args.plcmID + '_' + args.partID; - } - - function _getRD () { - try { - return _getItem(_getResltStatusFileName()); - } catch (err) { - return null; - } - } - - function _setRD (fileData) { - try { - _setItem(_getResltStatusFileName(), fileData); - return true; - } catch (err) { - return false; - } - } - - function _getGdpr() { - return args.gdprObj; - } - - function _getMediaObj() { - return args.mediaObj; - } - - function _getResltStatusFileName () { - if (args.lmod === true) return _resFilePref + args.lplcmID + '_' + args.partID; - else return _resFilePref + args.plcmID + '_' + args.partID; - } - - function _setItem (name, data) { - var stgFileObj = { - ver: _pcmClientVersion, - ts: Date.now(), - }; - - if (typeof data === 'string') { - stgFileObj.objtype = false; - stgFileObj.memdata = data; - } else { - stgFileObj.objtype = true; - stgFileObj.memdata = JSON.stringify(data); - } - - var stgFileStr = JSON.stringify(stgFileObj); - - storage.setDataInLocalStorage(name, stgFileStr); - - return true; - } - - function _getItem (name) { - try { - var obStgFileStr = storage.getDataFromLocalStorage(name); - if (obStgFileStr === null) return null; - - var stgFileObj = JSON.parse(obStgFileStr); - - if (stgFileObj.ver !== _pcmClientVersion) throw { message: 'version_error' }; - - if (stgFileObj.objtype === true) return JSON.parse(stgFileObj.memdata); - else return '' + stgFileObj.memdata; - } catch (err) { - return null; - } - } -}; - -registerBidder(spec); diff --git a/modules/smartrtbBidAdapter.js b/modules/resetdigitalBidAdapter.js similarity index 75% rename from modules/smartrtbBidAdapter.js rename to modules/resetdigitalBidAdapter.js index de303f9e4b2..255ee32629c 100644 --- a/modules/smartrtbBidAdapter.js +++ b/modules/resetdigitalBidAdapter.js @@ -1,39 +1,28 @@ -import * as utils from '../src/utils.js'; +import { timestamp, deepAccess, getOrigin } from '../src/utils.js'; import { config } from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -const BIDDER_CODE = 'smartrtb'; - -function getDomain () { - if (!utils.inIframe()) { - return window.location.hostname - } - let origins = window.document.location.ancestorOrigins - if (origins && origins.length > 0) { - return origins[origins.length - 1] - } -} +const BIDDER_CODE = 'resetdigital'; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ 'banner', 'video' ], - aliases: ['rdigital'], isBidRequestValid: function(bid) { - return (bid.params.pubId !== null || bid.params.zoneId !== null); + return (!!(bid.params.pubId || bid.params.zoneId)); }, buildRequests: function(validBidRequests, bidderRequest) { let stack = (bidderRequest.refererInfo && - bidderRequest.refererInfo.stack ? bidderRequest.refererInfo + bidderRequest.refererInfo.stack ? bidderRequest.refererInfo.stack : []) let spb = (config.getConfig('userSync') && config.getConfig('userSync').syncsPerBidder) ? config.getConfig('userSync').syncsPerBidder : 5 const payload = { - start_time: utils.timestamp(), + start_time: timestamp(), language: window.navigator.userLanguage || window.navigator.language, site: { - domain: getDomain(), + domain: getOrigin(), iframe: !bidderRequest.refererInfo.reachedTop, url: stack && stack.length > 0 ? [stack.length - 1] : null, https: (window.location.protocol === 'https:'), @@ -55,18 +44,18 @@ export const spec = { let req = validBidRequests[x] payload.imps.push({ + pub_id: req.params.pubId, zone_id: req.params.zoneId, bid_id: req.bidId, imp_id: req.transactionId, sizes: req.sizes, force_bid: req.params.forceBid, - media_types: utils.deepAccess(req, 'mediaTypes'), - has_renderer: (req.renderer !== undefined) + media_types: deepAccess(req, 'mediaTypes') }); } let params = validBidRequests[0].params - let url = params.endpoint ? params.endpoint : 'https://market-global.smrtb.com/json/publisher/prebid' + let url = params.endpoint ? params.endpoint : '//ads.resetsrv.com' return { method: 'POST', url: url, @@ -100,13 +89,16 @@ export const spec = { creativeId: bid.crid, dealId: bid.deal_id, netRevenue: true, - currency: 'USD' + currency: 'USD', + meta: { + advertiserDomains: bid.adomain + } }) } return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { const syncs = [] if (!serverResponses.length || !serverResponses[0].body) { @@ -118,11 +110,23 @@ export const spec = { return syncs } + let gdprParams = null + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}` + } else { + gdprParams = `gdpr_consent=${gdprConsent.consentString}` + } + } + for (let x = 0; x < pixels.length; x++) { let pixel = pixels[x] if ((pixel.type === 'iframe' && syncOptions.iframeEnabled) || (pixel.type === 'image' && syncOptions.pixelEnabled)) { + if (gdprParams && gdprParams.length) { + pixel = (pixel.indexOf('?') === -1 ? '?' : '&') + gdprParams + } syncs.push(pixel) } } diff --git a/modules/resetdigitalBidAdapter.md b/modules/resetdigitalBidAdapter.md new file mode 100644 index 00000000000..2f9f69b5e84 --- /dev/null +++ b/modules/resetdigitalBidAdapter.md @@ -0,0 +1,37 @@ +# Overview + +``` +Module Name: ResetDigital Bidder Adapter +Module Type: Bidder Adapter +Maintainer: bruce@resetdigital.co +``` + +# Description + +Prebid adapter for Reset Digital. Requires approval and account setup. +Video is supported but requires a publisher supplied renderer at this time. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'your-div', + mediaTypes: { + banner: { + sizes: [[300,250]] + } + }, + bids: [ + { + bidder: "resetdigital", + params: { + pubId: "your-pub-id", + forceBid: true + } + } + ] + } + ]; +``` diff --git a/modules/resultsmediaBidAdapter.js b/modules/resultsmediaBidAdapter.js deleted file mode 100644 index beb9991e1e2..00000000000 --- a/modules/resultsmediaBidAdapter.js +++ /dev/null @@ -1,269 +0,0 @@ -'use strict'; - -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; - -function ResultsmediaAdapter() { - this.code = 'resultsmedia'; - this.aliases = ['resultsmedia']; - this.supportedMediaTypes = [VIDEO, BANNER]; - - let SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6]; - let SUPPORTED_VIDEO_MIMES = ['video/mp4']; - let SUPPORTED_VIDEO_PLAYBACK_METHODS = [1, 2, 3, 4]; - let SUPPORTED_VIDEO_DELIVERY = [1]; - let SUPPORTED_VIDEO_API = [1, 2, 5]; - let slotsToBids = {}; - let that = this; - let version = '2.1'; - - this.isBidRequestValid = function (bid) { - return !!(bid.params && bid.params.zoneId); - }; - - this.getUserSyncs = function (syncOptions, responses, gdprConsent) { - return []; - }; - - function frameImp(BRs, bidderRequest) { - var impList = []; - var isSecure = 0; - if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.stack.length) { - // clever trick to get the protocol - var el = document.createElement('a'); - el.href = bidderRequest.refererInfo.stack[0]; - isSecure = (el.protocol == 'https:') ? 1 : 0; - } - for (var i = 0; i < BRs.length; i++) { - slotsToBids[BRs[i].adUnitCode] = BRs[i]; - var impObj = {}; - impObj.id = BRs[i].adUnitCode; - impObj.secure = isSecure; - - if (utils.deepAccess(BRs[i], 'mediaTypes.banner') || utils.deepAccess(BRs[i], 'mediaType') === 'banner') { - let banner = frameBanner(BRs[i]); - if (banner) { - impObj.banner = banner; - } - } - if (utils.deepAccess(BRs[i], 'mediaTypes.video') || utils.deepAccess(BRs[i], 'mediaType') === 'video') { - impObj.video = frameVideo(BRs[i]); - } - if (!(impObj.banner || impObj.video)) { - continue; - } - impObj.ext = frameExt(BRs[i]); - impList.push(impObj); - } - return impList; - } - - function frameSite(bidderRequest) { - var site = { - domain: '', - page: '', - ref: '' - } - if (bidderRequest && bidderRequest.refererInfo) { - var ri = bidderRequest.refererInfo; - site.ref = ri.referer; - - if (ri.stack.length) { - site.page = ri.stack[ri.stack.length - 1]; - - // clever trick to get the domain - var el = document.createElement('a'); - el.href = ri.stack[0]; - site.domain = el.hostname; - } - } - return site; - } - - function frameDevice() { - return { - ua: navigator.userAgent, - ip: '', // Empty Ip string is required, server gets the ip from HTTP header - dnt: utils.getDNT() ? 1 : 0, - } - } - - function getValidSizeSet(dimensionList) { - let w = parseInt(dimensionList[0]); - let h = parseInt(dimensionList[1]); - // clever check for NaN - if (! (w !== w || h !== h)) { // eslint-disable-line - return [w, h]; - } - return false; - } - - function frameBanner(adUnit) { - // adUnit.sizes is scheduled to be deprecated, continue its support but prefer adUnit.mediaTypes.banner - var sizeList = adUnit.sizes; - if (adUnit.mediaTypes && adUnit.mediaTypes.banner) { - sizeList = adUnit.mediaTypes.banner.sizes; - } - var sizeStringList = utils.parseSizesInput(sizeList); - var format = []; - sizeStringList.forEach(function(size) { - if (size) { - var dimensionList = getValidSizeSet(size.split('x')); - if (dimensionList) { - format.push({ - 'w': dimensionList[0], - 'h': dimensionList[1], - }); - } - } - }); - if (format.length) { - return { - 'format': format - }; - } - - return false; - } - - function frameVideo(bid) { - var size = []; - if (utils.deepAccess(bid, 'mediaTypes.video.playerSize')) { - var dimensionSet = bid.mediaTypes.video.playerSize; - if (utils.isArray(bid.mediaTypes.video.playerSize[0])) { - dimensionSet = bid.mediaTypes.video.playerSize[0]; - } - var validSize = getValidSizeSet(dimensionSet) - if (validSize) { - size = validSize; - } - } - return { - mimes: utils.deepAccess(bid, 'mediaTypes.video.mimes') || SUPPORTED_VIDEO_MIMES, - protocols: utils.deepAccess(bid, 'mediaTypes.video.protocols') || SUPPORTED_VIDEO_PROTOCOLS, - w: size[0], - h: size[1], - startdelay: utils.deepAccess(bid, 'mediaTypes.video.startdelay') || 0, - skip: utils.deepAccess(bid, 'mediaTypes.video.skip') || 0, - playbackmethod: utils.deepAccess(bid, 'mediaTypes.video.playbackmethod') || SUPPORTED_VIDEO_PLAYBACK_METHODS, - delivery: utils.deepAccess(bid, 'mediaTypes.video.delivery') || SUPPORTED_VIDEO_DELIVERY, - api: utils.deepAccess(bid, 'mediaTypes.video.api') || SUPPORTED_VIDEO_API, - } - } - - function frameExt(bid) { - return { - bidder: { - zoneId: bid.params['zoneId'] - } - } - } - - function frameBid(BRs, bidderRequest) { - let bid = { - id: BRs[0].bidderRequestId, - imp: frameImp(BRs, bidderRequest), - site: frameSite(bidderRequest), - device: frameDevice(), - user: { - ext: { - consent: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? bidderRequest.gdprConsent.consentString : '' - } - }, - at: 1, - tmax: 1000, - regs: { - ext: { - gdpr: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? Boolean(bidderRequest.gdprConsent.gdprApplies & 1) : false - } - } - }; - if (BRs[0].schain) { - bid.source = { - 'ext': { - 'schain': BRs[0].schain - } - } - } - return bid; - } - - function getFirstParam(key, validBidRequests) { - for (let i = 0; i < validBidRequests.length; i++) { - if (validBidRequests[i].params && validBidRequests[i].params[key]) { - return validBidRequests[i].params[key]; - } - } - } - - this.buildRequests = function (BRs, bidderRequest) { - let fallbackZoneId = getFirstParam('zoneId', BRs); - if (fallbackZoneId === undefined || BRs.length < 1) { - return []; - } - - var uri = 'https://bid306.rtbsrv.com/bidder/?bid=3mhdom&zoneId=' + fallbackZoneId; - - var fat = /(^v|(\.0)+$)/gi; - var prebidVersion = '$prebid.version$'; - uri += '&hbv=' + prebidVersion.replace(fat, '') + ',' + version.replace(fat, ''); - - var bidRequest = frameBid(BRs, bidderRequest); - if (!bidRequest.imp.length) { - return {}; - } - - return { - method: 'POST', - url: uri, - data: JSON.stringify(bidRequest) - }; - }; - - this.interpretResponse = function (serverResponse) { - let responses = serverResponse.body || []; - let bids = []; - let i = 0; - - if (responses.seatbid) { - let temp = []; - for (i = 0; i < responses.seatbid.length; i++) { - for (let j = 0; j < responses.seatbid[i].bid.length; j++) { - temp.push(responses.seatbid[i].bid[j]); - } - } - responses = temp; - } - - for (i = 0; i < responses.length; i++) { - let bid = responses[i]; - let bidRequest = slotsToBids[bid.impid]; - let bidResponse = { - requestId: bidRequest.id, - bidderCode: that.code, - cpm: parseFloat(bid.price), - width: bid.w, - height: bid.h, - creativeId: bid.crid, - currency: 'USD', - netRevenue: true, - ttl: 350 - }; - - if (bidRequest.mediaTypes && bidRequest.mediaTypes.video) { - bidResponse.vastUrl = bid.adm; - bidResponse.mediaType = 'video'; - bidResponse.ttl = 600; - } else { - bidResponse.ad = bid.adm; - } - bids.push(bidResponse); - } - - return bids; - }; -} - -export const spec = new ResultsmediaAdapter(); -registerBidder(spec); diff --git a/modules/revcontentBidAdapter.js b/modules/revcontentBidAdapter.js index b429f94eae0..0888e5ad1b4 100644 --- a/modules/revcontentBidAdapter.js +++ b/modules/revcontentBidAdapter.js @@ -2,7 +2,8 @@ 'use strict'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { triggerPixel, isFn, deepAccess, getAdUnitSizes, parseGPTSingleSizeArrayToRtbSize, _map } from '../src/utils.js'; const BIDDER_CODE = 'revcontent'; const NATIVE_PARAMS = { @@ -21,12 +22,13 @@ const NATIVE_PARAMS = { type: 1 } }; +const STYLE_EXTRA = ''; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: ['native'], + supportedMediaTypes: [BANNER, NATIVE], isBidRequestValid: function (bid) { - return (typeof bid.params.apiKey !== 'undefined' && typeof bid.params.userId !== 'undefined' && bid.hasOwnProperty('nativeParams')); + return (typeof bid.params.apiKey !== 'undefined' && typeof bid.params.userId !== 'undefined'); }, buildRequests: (validBidRequests, bidderRequest) => { const userId = validBidRequests[0].params.userId; @@ -55,66 +57,7 @@ export const spec = { endpoint = endpoint + '&widgetId=' + widgetId; } - let bidfloor = 0.1; - if (!isNaN(validBidRequests[0].params.bidfloor) && validBidRequests[0].params.bidfloor > 0) { - bidfloor = validBidRequests[0].params.bidfloor; - } - - const imp = validBidRequests.map((bid, id) => { - if (bid.hasOwnProperty('nativeParams')) { - const assets = utils._map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - const asset = { - required: bidParams.required & 1 - }; - if (props) { - asset.id = props.id; - let wmin, hmin, w, h; - let aRatios = bidParams.aspect_ratios; - - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - wmin = aRatios.min_width || 0; - hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; - } - - asset[props.name] = { - len: bidParams.len, - type: props.type, - wmin, - hmin, - w, - h - }; - - return asset; - } - }).filter(Boolean); - - return { - id: id + 1, - tagid: bid.params.mid, - bidderRequestId: bid.bidderRequestId, - auctionId: bid.auctionId, - transactionId: bid.transactionId, - native: { - request: { - ver: '1.1', - context: 2, - contextsubtype: 21, - plcmttype: 1, - plcmtcnt: 1, - assets: assets - }, - ver: '1.1', - battr: [1, 3, 8, 11, 17] - }, - instl: 0, - bidfloor: bidfloor, - secure: '1' - }; - } - }); + const imp = validBidRequests.map((bid, id) => buildImp(bid, id)); let data = { id: bidderRequest.auctionId, @@ -123,7 +66,6 @@ export const spec = { id: widgetId, domain: domain, page: refererInfo, - cat: ['IAB17'], publisher: { id: userId, domain: domain @@ -136,23 +78,7 @@ export const spec = { user: { id: 1 }, - at: 2, - bcat: [ - 'IAB24', - 'IAB25', - 'IAB25-1', - 'IAB25-2', - 'IAB25-3', - 'IAB25-4', - 'IAB25-5', - 'IAB25-6', - 'IAB25-7', - 'IAB26', - 'IAB26-1', - 'IAB26-2', - 'IAB26-3', - 'IAB26-4' - ] + at: 2 }; serverRequests.push({ method: 'POST', @@ -166,63 +92,76 @@ export const spec = { return serverRequests; }, - interpretResponse: function (serverResponse, originalBidRequest) { - if (!serverResponse.body) { - return; + interpretResponse: function (serverResponse, serverRequest) { + let response = serverResponse.body; + if ((!response) || (!response.seatbid)) { + return []; } - const seatbid = serverResponse.body.seatbid[0]; - const bidResponses = []; - - for (var x in seatbid.bid) { - let adm = JSON.parse(seatbid.bid[x]['adm']); - let ad = { - clickUrl: adm.link.url - }; - adm.assets.forEach(asset => { - switch (asset.id) { - case 3: - ad['image'] = { - url: asset.img.url, - height: 1, - width: 1 - }; - break; - case 0: - ad['title'] = asset.title.text; - break; - case 5: - ad['sponsoredBy'] = asset.data.value; - break; - } - }); - - var size = originalBidRequest.bid[0].params.size; - - const bidResponse = { - bidder: BIDDER_CODE, - requestId: originalBidRequest.bid[0].bidId, - cpm: seatbid.bid[x]['price'], - creativeId: seatbid.bid[x]['adid'], - currency: 'USD', - netRevenue: true, + let rtbRequest = JSON.parse(serverRequest.data); + let rtbBids = response.seatbid + .map(seatbid => seatbid.bid) + .reduce((a, b) => a.concat(b), []); + + return rtbBids.map(rtbBid => { + const bidIndex = +rtbBid.impid - 1; + let imp = rtbRequest.imp.filter(imp => imp.id.toString() === rtbBid.impid)[0]; + + let prBid = { + requestId: serverRequest.bid[bidIndex].bidId, + cpm: rtbBid.price, + creativeId: rtbBid.crid, + nurl: rtbBid.nurl, + currency: response.cur || 'USD', ttl: 360, - nurl: seatbid.bid[x]['nurl'], - bidderCode: 'revcontent', - mediaType: 'native', - native: ad, - width: size.width, - height: size.height, - ad: displayNative(ad, getTemplate(size, originalBidRequest.bid[0].params.template)) + netRevenue: true, + bidder: 'revcontent', + bidderCode: 'revcontent' }; + if ('banner' in imp) { + prBid.mediaType = BANNER; + prBid.width = rtbBid.w; + prBid.height = rtbBid.h; + prBid.ad = STYLE_EXTRA + rtbBid.adm; + } else if ('native' in imp) { + let adm = JSON.parse(rtbBid.adm); + let ad = { + clickUrl: adm.link.url + }; - bidResponses.push(bidResponse); - } + adm.assets.forEach(asset => { + switch (asset.id) { + case 3: + ad['image'] = { + url: asset.img.url, + height: 1, + width: 1 + }; + break; + case 0: + ad['title'] = asset.title.text; + break; + case 5: + ad['sponsoredBy'] = asset.data.value || 'Revcontent'; + break; + } + }); + var size = serverRequest.bid[0].params.size; + prBid.width = size.width; + prBid.height = size.height; + + prBid.mediaType = NATIVE; + prBid.native = ad; + prBid.ad = displayNative(ad, getTemplate(serverRequest.bid[0].params.size, serverRequest.bid[0].params.template)); + } - return bidResponses; + return prBid; + }); }, onBidWon: function (bid) { - utils.triggerPixel(bid.nurl); + if (bid.nurl) { + triggerPixel(bid.nurl); + } return true; } }; @@ -273,3 +212,80 @@ function extractHostname(url) { return hostname; } + +function buildImp(bid, id) { + let bidfloor; + if (isFn(bid.getFloor)) { + bidfloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }).floor; + } else { + bidfloor = deepAccess(bid, `params.bidfloor`) || 0.1; + } + + let imp = { + id: id + 1, + tagid: bid.adUnitCode, + bidderRequestId: bid.bidderRequestId, + auctionId: bid.auctionId, + transactionId: bid.transactionId, + instl: 0, + bidfloor: bidfloor, + secure: '1' + }; + + let bannerReq = deepAccess(bid, `mediaTypes.banner`); + let nativeReq = deepAccess(bid, `mediaTypes.native`); + if (bannerReq) { + let sizes = getAdUnitSizes(bid); + imp.banner = { + w: sizes[0][0], + h: sizes[0][1], + format: sizes.map(wh => parseGPTSingleSizeArrayToRtbSize(wh)), + } + } else if (nativeReq) { + const assets = _map(bid.nativeParams, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1 + }; + if (props) { + asset.id = props.id; + let wmin, hmin, w, h; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } + + asset[props.name] = { + len: bidParams.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } + }).filter(Boolean); + imp.native = { + request: { + ver: '1.1', + context: 2, + contextsubtype: 21, + plcmttype: 1, + plcmtcnt: 1, + assets: assets + }, + ver: '1.1', + battr: [1, 3, 8, 11, 17] + }; + } + return imp; +} diff --git a/modules/rhythmoneBidAdapter.js b/modules/rhythmoneBidAdapter.js index fa090044f05..d0e399ab7e3 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -1,6 +1,7 @@ + 'use strict'; -import * as utils from '../src/utils.js'; +import { deepAccess, getDNT, parseSizesInput, isArray } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -38,16 +39,16 @@ function RhythmOneBidAdapter() { slotsToBids[BRs[i].adUnitCode] = BRs[i]; var impObj = {}; impObj.id = BRs[i].adUnitCode; - impObj.bidfloor = parseFloat(utils.deepAccess(BRs[i], 'params.floor')) || 0; + impObj.bidfloor = 0; impObj.secure = isSecure; - if (utils.deepAccess(BRs[i], 'mediaTypes.banner') || utils.deepAccess(BRs[i], 'mediaType') === 'banner') { + if (deepAccess(BRs[i], 'mediaTypes.banner') || deepAccess(BRs[i], 'mediaType') === 'banner') { let banner = frameBanner(BRs[i]); if (banner) { impObj.banner = banner; } } - if (utils.deepAccess(BRs[i], 'mediaTypes.video') || utils.deepAccess(BRs[i], 'mediaType') === 'video') { + if (deepAccess(BRs[i], 'mediaTypes.video') || deepAccess(BRs[i], 'mediaType') === 'video') { impObj.video = frameVideo(BRs[i]); } if (!(impObj.banner || impObj.video)) { @@ -85,7 +86,7 @@ function RhythmOneBidAdapter() { return { ua: navigator.userAgent, ip: '', // Empty Ip string is required, server gets the ip from HTTP header - dnt: utils.getDNT() ? 1 : 0, + dnt: getDNT() ? 1 : 0, } } @@ -105,7 +106,7 @@ function RhythmOneBidAdapter() { if (adUnit.mediaTypes && adUnit.mediaTypes.banner) { sizeList = adUnit.mediaTypes.banner.sizes; } - var sizeStringList = utils.parseSizesInput(sizeList); + var sizeStringList = parseSizesInput(sizeList); var format = []; sizeStringList.forEach(function(size) { if (size) { @@ -129,9 +130,9 @@ function RhythmOneBidAdapter() { function frameVideo(bid) { var size = []; - if (utils.deepAccess(bid, 'mediaTypes.video.playerSize')) { + if (deepAccess(bid, 'mediaTypes.video.playerSize')) { var dimensionSet = bid.mediaTypes.video.playerSize; - if (utils.isArray(bid.mediaTypes.video.playerSize[0])) { + if (isArray(bid.mediaTypes.video.playerSize[0])) { dimensionSet = bid.mediaTypes.video.playerSize[0]; } var validSize = getValidSizeSet(dimensionSet) @@ -140,15 +141,15 @@ function RhythmOneBidAdapter() { } } return { - mimes: utils.deepAccess(bid, 'mediaTypes.video.mimes') || SUPPORTED_VIDEO_MIMES, - protocols: utils.deepAccess(bid, 'mediaTypes.video.protocols') || SUPPORTED_VIDEO_PROTOCOLS, + mimes: deepAccess(bid, 'mediaTypes.video.mimes') || SUPPORTED_VIDEO_MIMES, + protocols: deepAccess(bid, 'mediaTypes.video.protocols') || SUPPORTED_VIDEO_PROTOCOLS, w: size[0], h: size[1], - startdelay: utils.deepAccess(bid, 'mediaTypes.video.startdelay') || 0, - skip: utils.deepAccess(bid, 'mediaTypes.video.skip') || 0, - playbackmethod: utils.deepAccess(bid, 'mediaTypes.video.playbackmethod') || SUPPORTED_VIDEO_PLAYBACK_METHODS, - delivery: utils.deepAccess(bid, 'mediaTypes.video.delivery') || SUPPORTED_VIDEO_DELIVERY, - api: utils.deepAccess(bid, 'mediaTypes.video.api') || SUPPORTED_VIDEO_API, + startdelay: deepAccess(bid, 'mediaTypes.video.startdelay') || 0, + skip: deepAccess(bid, 'mediaTypes.video.skip') || 0, + playbackmethod: deepAccess(bid, 'mediaTypes.video.playbackmethod') || SUPPORTED_VIDEO_PLAYBACK_METHODS, + delivery: deepAccess(bid, 'mediaTypes.video.delivery') || SUPPORTED_VIDEO_DELIVERY, + api: deepAccess(bid, 'mediaTypes.video.api') || SUPPORTED_VIDEO_API, } } @@ -170,14 +171,14 @@ function RhythmOneBidAdapter() { device: frameDevice(), user: { ext: { - consent: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? bidderRequest.gdprConsent.consentString : '' + consent: deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? bidderRequest.gdprConsent.consentString : '' } }, at: 1, tmax: 1000, regs: { ext: { - gdpr: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? Boolean(bidderRequest.gdprConsent.gdprApplies & 1) : false + gdpr: deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? Boolean(bidderRequest.gdprConsent.gdprApplies & 1) : false } } }; diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 5e2a5e1bff5..b49d7c5584c 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -1,7 +1,7 @@ +import {isEmpty, deepAccess, isStr} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'richaudience'; @@ -52,17 +52,22 @@ export const spec = { videoData: raiGetVideoInfo(bid), scr_rsl: raiGetResolution(), cpuc: (typeof window.navigator != 'undefined' ? window.navigator.hardwareConcurrency : null), - kws: (!utils.isEmpty(bid.params.keywords) ? bid.params.keywords : null) + kws: (!isEmpty(bid.params.keywords) ? bid.params.keywords : null), + schain: bid.schain }; REFERER = (typeof bidderRequest.refererInfo.referer != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.referer) : null) payload.gdpr_consent = ''; - payload.gdpr = null; + payload.gdpr = false; if (bidderRequest && bidderRequest.gdprConsent) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - payload.gdpr = bidderRequest.gdprConsent.gdprApplies; + if (typeof bidderRequest.gdprConsent.gdprApplies != 'undefined') { + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; + } + if (typeof bidderRequest.gdprConsent.consentString != 'undefined') { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } } var payloadString = JSON.stringify(payload); @@ -97,7 +102,8 @@ export const spec = { netRevenue: response.netRevenue, currency: response.currency, ttl: response.ttl, - dealId: response.dealId, + meta: response.adomain, + dealId: response.dealId }; if (response.media_type === 'video') { @@ -191,6 +197,13 @@ function raiGetSizes(bid) { function raiGetDemandType(bid) { let raiFormat = 'display'; + if (typeof bid.sizes != 'undefined') { + bid.sizes.forEach(function (sz) { + if ((sz[0] == '1800' && sz[1] == '1000') || (sz[0] == '1' && sz[1] == '1')) { + raiFormat = 'skin' + } + }) + } if (bid.mediaTypes != undefined) { if (bid.mediaTypes.video != undefined) { raiFormat = 'video'; @@ -219,19 +232,19 @@ function raiSetEids(bid) { let eids = []; if (bid && bid.userId) { - raiSetUserId(bid, eids, 'id5-sync.com', utils.deepAccess(bid, `userId.id5id.uid`)); - raiSetUserId(bid, eids, 'pubcommon', utils.deepAccess(bid, `userId.pubcid`)); - raiSetUserId(bid, eids, 'criteo.com', utils.deepAccess(bid, `userId.criteoId`)); - raiSetUserId(bid, eids, 'liveramp.com', utils.deepAccess(bid, `userId.idl_env`)); - raiSetUserId(bid, eids, 'liveintent.com', utils.deepAccess(bid, `userId.lipb.lipbid`)); - raiSetUserId(bid, eids, 'adserver.org', utils.deepAccess(bid, `userId.tdid`)); + raiSetUserId(bid, eids, 'id5-sync.com', deepAccess(bid, `userId.id5id.uid`)); + raiSetUserId(bid, eids, 'pubcommon', deepAccess(bid, `userId.pubcid`)); + raiSetUserId(bid, eids, 'criteo.com', deepAccess(bid, `userId.criteoId`)); + raiSetUserId(bid, eids, 'liveramp.com', deepAccess(bid, `userId.idl_env`)); + raiSetUserId(bid, eids, 'liveintent.com', deepAccess(bid, `userId.lipb.lipbid`)); + raiSetUserId(bid, eids, 'adserver.org', deepAccess(bid, `userId.tdid`)); } return eids; } function raiSetUserId(bid, eids, source, value) { - if (utils.isStr(value)) { + if (isStr(value)) { eids.push({ userId: value, source: source @@ -294,8 +307,8 @@ function raiGetFloor(bid, config) { raiFloor = bid.params.bidfloor; } else if (typeof bid.getFloor == 'function') { let floorSpec = bid.getFloor({ - currency: config.getConfig('currency.adServerCurrency'), - mediaType: bid.mediaType.banner ? 'banner' : 'video', + currency: config.getConfig('floors.data.currency') != null ? config.getConfig('floors.data.currency') : 'USD', + mediaType: typeof bid.mediaTypes['banner'] == 'object' ? 'banner' : 'video', size: '*' }) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index e3265ad5d3e..9aca2491386 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -1,12 +1,13 @@ +import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; import {VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; const SUPPORTED_AD_TYPES = [VIDEO]; const BIDDER_CODE = 'rise'; -const BIDDER_VERSION = '4.0.0'; +const ADAPTER_VERSION = '5.0.0'; const TTL = 360; +const CURRENCY = 'USD'; const SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; const MODES = { PRODUCTION: 'hb', @@ -19,10 +20,21 @@ const SUPPORTED_SYNC_METHODS = { export const spec = { code: BIDDER_CODE, - version: BIDDER_VERSION, + gvlid: 1043, + version: ADAPTER_VERSION, supportedMediaTypes: SUPPORTED_AD_TYPES, - isBidRequestValid: function(bidRequest) { - return !!(bidRequest.params.org); + isBidRequestValid: function (bidRequest) { + if (!bidRequest.params) { + logWarn('no params have been set to Rise adapter'); + return false; + } + + if (!bidRequest.params.org) { + logWarn('org is a mandatory param for Rise adapter'); + return false; + } + + return true; }, buildRequests: function (bidRequests, bidderRequest) { if (bidRequests.length === 0) { @@ -37,7 +49,7 @@ export const spec = { return requests; }, - interpretResponse: function({body}) { + interpretResponse: function ({body}) { const bidResponses = []; const bidResponse = { @@ -50,14 +62,19 @@ export const spec = { netRevenue: body.netRevenue, ttl: body.ttl || TTL, vastXml: body.vastXml, + nurl: body.nurl, mediaType: VIDEO }; + if (body.adomain && body.adomain.length) { + bidResponse.meta = {}; + bidResponse.meta.advertiserDomains = body.adomain + } bidResponses.push(bidResponse); return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { if (syncOptions.iframeEnabled && response.body.userSyncURL) { @@ -66,7 +83,7 @@ export const spec = { url: response.body.userSyncURL }); } - if (syncOptions.pixelEnabled && utils.isArray(response.body.userSyncPixels)) { + if (syncOptions.pixelEnabled && isArray(response.body.userSyncPixels)) { const pixels = response.body.userSyncPixels.map(pixel => { return { type: 'image', @@ -77,11 +94,38 @@ export const spec = { } } return syncs; - } -}; + }, + onBidWon: function (bid) { + if (bid == null) { + return; + } + + logInfo('onBidWon:', bid); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } + }, +} registerBidder(spec); +/** + * Get floor price + * @param bid {bid} + * @returns {Number} + */ +function getFloor(bid) { + if (!isFn(bid.getFloor)) { + return 0; + } + let floorResult = bid.getFloor({ + currency: CURRENCY, + mediaType: VIDEO, + size: '*' + }); + return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; +} + /** * Build the video request * @param bid {bid} @@ -104,7 +148,7 @@ function buildVideoRequest(bid, bidderRequest) { * @returns {Array} */ function getSizes(bid) { - if (utils.deepAccess(bid, 'mediaTypes.video.sizes')) { + if (deepAccess(bid, 'mediaTypes.video.sizes')) { return bid.mediaTypes.video.sizes[0]; } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { return bid.sizes[0]; @@ -118,7 +162,7 @@ function getSizes(bid) { * @returns {string} */ function getSupplyChain(schainObject) { - if (utils.isEmpty(schainObject)) { + if (isEmpty(schainObject)) { return ''; } let scStr = `${schainObject.ver},${schainObject.complete}`; @@ -126,7 +170,7 @@ function getSupplyChain(schainObject) { scStr += '!'; scStr += `${getEncodedValIfNotEmpty(node.asi)},`; scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${getEncodedValIfNotEmpty(node.hp)},`; + scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; scStr += `${getEncodedValIfNotEmpty(node.rid)},`; scStr += `${getEncodedValIfNotEmpty(node.name)},`; scStr += `${getEncodedValIfNotEmpty(node.domain)}`; @@ -140,7 +184,7 @@ function getSupplyChain(schainObject) { * @returns {string} */ function getEncodedValIfNotEmpty(val) { - return !utils.isEmpty(val) ? encodeURIComponent(val) : ''; + return !isEmpty(val) ? encodeURIComponent(val) : ''; } /** @@ -170,8 +214,8 @@ function isSyncMethodAllowed(syncRule, bidderCode) { return false; } const isInclude = syncRule.filter === 'include'; - const bidders = utils.isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && utils.contains(bidders, bidderCode); + const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && contains(bidders, bidderCode); } /** @@ -185,6 +229,27 @@ function getEndpoint(testMode) { : SELLER_ENDPOINT + MODES.PRODUCTION; } +/** + * get device type + * @param uad {ua} + * @returns {string} + */ +function getDeviceType(ua) { + if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i + .test(ua.toLowerCase())) { + return '5'; + } + if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i + .test(ua.toLowerCase())) { + return '4'; + } + if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i + .test(ua.toLowerCase())) { + return '3'; + } + return '1'; +} + /** * Generate query parameters for the request * @param bid {bid} @@ -192,32 +257,87 @@ function getEndpoint(testMode) { * @returns {Object} */ function generateParameters(bid, bidderRequest) { + const {params} = bid; const timeout = config.getConfig('bidderTimeout'); - const { syncEnabled, filterSettings } = config.getConfig('userSync') || {}; - const [ width, height ] = getSizes(bid); - const { params } = bid; - const { bidderCode } = bidderRequest; + const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; + const [width, height] = getSizes(bid); + const {bidderCode} = bidderRequest; const domain = window.location.hostname; + // fix floor price in case of NAN + if (isNaN(params.floorPrice)) { + params.floorPrice = 0; + } + const requestParams = { - auction_start: utils.timestamp(), - ad_unit_code: utils.getBidIdParameter('adUnitCode', bid), + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$', + adapter_version: ADAPTER_VERSION, + auction_start: timestamp(), + ad_unit_code: getBidIdParameter('adUnitCode', bid), tmax: timeout, width: width, height: height, publisher_id: params.org, - floor_price: params.floorPrice, + floor_price: Math.max(getFloor(bid), params.floorPrice), ua: navigator.userAgent, - bid_id: utils.getBidIdParameter('bidId', bid), - bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), - transaction_id: utils.getBidIdParameter('transactionId', bid), - session_id: params.sessionId || utils.getBidIdParameter('auctionId', bid), - is_wrapper: !!params.isWrapper, + bid_id: getBidIdParameter('bidId', bid), + bidder_request_id: getBidIdParameter('bidderRequestId', bid), + transaction_id: getBidIdParameter('transactionId', bid), + session_id: getBidIdParameter('auctionId', bid), publisher_name: domain, site_domain: domain, - bidder_version: BIDDER_VERSION + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + device_type: getDeviceType(navigator.userAgent) }; + const userIdsParam = getBidIdParameter('userId', bid); + if (userIdsParam) { + requestParams.userIds = JSON.stringify(userIdsParam); + } + + const ortb2Metadata = config.getConfig('ortb2') || {}; + if (ortb2Metadata.site) { + requestParams.site_metadata = JSON.stringify(ortb2Metadata.site); + } + if (ortb2Metadata.user) { + requestParams.user_metadata = JSON.stringify(ortb2Metadata.user); + } + + const playbackMethod = deepAccess(bid, 'mediaTypes.video.playbackmethod'); + if (playbackMethod) { + requestParams.playback_method = playbackMethod; + } + const placement = deepAccess(bid, 'mediaTypes.video.placement'); + if (placement) { + requestParams.placement = placement; + } + const pos = deepAccess(bid, 'mediaTypes.video.pos'); + if (pos) { + requestParams.pos = pos; + } + const minduration = deepAccess(bid, 'mediaTypes.video.minduration'); + if (minduration) { + requestParams.min_duration = minduration; + } + const maxduration = deepAccess(bid, 'mediaTypes.video.maxduration'); + if (maxduration) { + requestParams.max_duration = maxduration; + } + const skip = deepAccess(bid, 'mediaTypes.video.skip'); + if (skip) { + requestParams.skip = skip; + } + const linearity = deepAccess(bid, 'mediaTypes.video.linearity'); + if (linearity) { + requestParams.linearity = linearity; + } + + if (params.placementId) { + requestParams.placement_id = params.placementId; + } + if (syncEnabled) { const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); if (allowedSyncMethod) { @@ -243,8 +363,8 @@ function generateParameters(bid, bidderRequest) { } if (bidderRequest && bidderRequest.refererInfo) { - requestParams.referrer = utils.deepAccess(bidderRequest, 'refererInfo.referer'); - requestParams.page_url = config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); + requestParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer'); + requestParams.page_url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); } return requestParams; diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 67eeab18226..83f8adfd645 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to Rise's demand sources. The Rise adapter requires setup and approval from the Rise. Please reach out to prebid-rise-engage@risecodes.com to create an Rise account. -The adapter supports Video(instream). For the integration, Rise returns content as vastXML and requires the publisher to define the cache url in config passed to Prebid for it to be valid in the auction. +The adapter supports Video(instream). # Bid Parameters ## Video @@ -22,7 +22,7 @@ The adapter supports Video(instream). For the integration, Rise returns content | ---- | ----- | ---- | ----------- | ------- | `org` | required | String | Rise publisher Id provided by your Rise representative | "56f91cd4d3e3660002000033" | `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 -| `ifa` | optional | String | The ID for advertisers (also referred to as "IDFA") | "XXX-XXX" +| `placementId` | optional | String | A unique placement identifier | "12345678" | `testMode` | optional | Boolean | This activates the test mode | false # Test Parameters @@ -42,7 +42,7 @@ var adUnits = [ params: { org: '56f91cd4d3e3660002000033', // Required floorPrice: 2.00, // Optional - ifa: 'XXX-XXX', // Optional + placementId: '12345678', // Optional testMode: false // Optional } }] diff --git a/modules/roxotAnalyticsAdapter.js b/modules/roxotAnalyticsAdapter.js index b6857d69f45..c9245d4ae08 100644 --- a/modules/roxotAnalyticsAdapter.js +++ b/modules/roxotAnalyticsAdapter.js @@ -1,3 +1,4 @@ +import { deepClone, getParameterByName, logInfo, logError } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; @@ -7,7 +8,6 @@ import { getStorageManager } from '../src/storageManager.js'; const storage = getStorageManager(); -const utils = require('../src/utils.js'); let ajax = ajaxBuilder(0); const DEFAULT_EVENT_URL = 'pa.rxthdr.com/v3'; @@ -153,7 +153,7 @@ function buildBidderRequest(auction, bidRequest) { function buildBidAfterTimeout(adUnitAuction, args) { return { - 'auction': utils.deepClone(adUnitAuction), + 'auction': deepClone(adUnitAuction), 'adUnit': extractAdUnitCode(args), 'bidder': extractBidder(args), 'cpm': args.cpm, @@ -170,7 +170,7 @@ function buildBidAfterTimeout(adUnitAuction, args) { function buildImpression(adUnitAuction, args) { return { 'isNew': checkIsNewFlag() ? 1 : 0, - 'auction': utils.deepClone(adUnitAuction), + 'auction': deepClone(adUnitAuction), 'adUnit': extractAdUnitCode(args), 'bidder': extractBidder(args), 'cpm': args.cpm, @@ -342,7 +342,7 @@ roxotAdapter.originEnableAnalytics = roxotAdapter.enableAnalytics; roxotAdapter.enableAnalytics = function (config) { if (this.initConfig(config)) { - logInfo('Analytics adapter enabled', initOptions); + _logInfo('Analytics adapter enabled', initOptions); roxotAdapter.originEnableAnalytics(config); } }; @@ -351,7 +351,7 @@ roxotAdapter.buildUtmTagData = function () { let utmTagData = {}; let utmTagsDetected = false; utmTags.forEach(function (utmTagKey) { - let utmTagValue = utils.getParameterByName(utmTagKey); + let utmTagValue = getParameterByName(utmTagKey); if (utmTagValue !== '') { utmTagsDetected = true; } @@ -374,11 +374,11 @@ roxotAdapter.buildUtmTagData = function () { roxotAdapter.initConfig = function (config) { let isCorrectConfig = true; initOptions = {}; - initOptions.options = utils.deepClone(config.options); + initOptions.options = deepClone(config.options); initOptions.publisherId = initOptions.options.publisherId || (initOptions.options.publisherIds[0]) || null; if (!initOptions.publisherId) { - logError('"options.publisherId" is empty'); + _logError('"options.publisherId" is empty'); isCorrectConfig = false; } @@ -407,7 +407,7 @@ function registerEvent(eventType, eventName, data) { sendEventCache.push(eventData); - logInfo('Register event', eventData); + _logInfo('Register event', eventData); (typeof initOptions.serverConfig === 'undefined') ? checkEventAfterTimeout() : checkSendEvent(); } @@ -427,7 +427,7 @@ function checkSendEvent() { let event = sendEventCache.shift(); let isNeedSend = initOptions.serverConfig[event.eventType] || 0; if (Number(isNeedSend) === 0) { - logInfo('Skip event ' + event.eventName, event); + _logInfo('Skip event ' + event.eventName, event); continue; } sendEvent(event.eventType, event.eventName, event.data); @@ -454,7 +454,7 @@ function sendEvent(eventType, eventName, data) { ajax( url, function () { - logInfo(eventName + ' sent', eventData); + _logInfo(eventName + ' sent', eventData); }, JSON.stringify(eventData), { @@ -490,12 +490,12 @@ function loadServerConfig() { ); } -function logInfo(message, meta) { - utils.logInfo(buildLogMessage(message), meta); +function _logInfo(message, meta) { + logInfo(buildLogMessage(message), meta); } -function logError(message) { - utils.logError(buildLogMessage(message)); +function _logError(message) { + logError(buildLogMessage(message)); } function buildLogMessage(message) { diff --git a/modules/rtbdemandBidAdapter.js b/modules/rtbdemandBidAdapter.js deleted file mode 100644 index be5fb39f53a..00000000000 --- a/modules/rtbdemandBidAdapter.js +++ /dev/null @@ -1,123 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'rtbdemand'; -const BIDDER_SERVER = 'bidding.rtbdemand.com'; -export const spec = { - code: BIDDER_CODE, - isBidRequestValid: function(bid) { - return !!(bid && bid.params && bid.params.zoneid); - }, - buildRequests: function(validBidRequests, bidderRequest) { - return validBidRequests.map(bidRequest => { - var server = bidRequest.params.server || BIDDER_SERVER; - var parse = getSize(bidderRequest.bids[0].sizes); - const payload = { - from: 'hb', - v: '1.0', - request_id: bidRequest.bidderRequestId, - imp_id: bidRequest.bidId, - aff: bidRequest.params.zoneid, - bid_floor: parseFloat(bidRequest.params.floor) > 0 ? bidRequest.params.floor : 0, - charset: document.charSet || document.characterSet, - site_domain: document.location.hostname, - site_page: window.location.href, - subid: 'hb', - flashver: getFlashVersion(), - tmax: bidderRequest.timeout, - hb: '1', - name: document.location.hostname, - width: parse.width, - height: parse.height, - device_width: screen.width, - device_height: screen.height, - dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, - secure: isSecure(), - make: navigator.vendor ? navigator.vendor : '', - }; - if (document.referrer) { - payload.referrer = document.referrer; - } - - return { - method: 'GET', - url: 'https://' + server + '/hb', - data: payload - }; - }); - }, - interpretResponse: function(serverResponse) { - serverResponse = serverResponse.body; - const bidResponses = []; - if (serverResponse && serverResponse.seatbid) { - serverResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => { - const bidResponse = { - requestId: bid.impid, - creativeId: bid.impid, - cpm: bid.price, - width: bid.w, - height: bid.h, - ad: bid.adm, - netRevenue: true, - currency: 'USD', - ttl: 360, - }; - - bidResponses.push(bidResponse); - })); - } - return bidResponses; - }, - getUserSyncs: function getUserSyncs(syncOptions) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: 'https://' + BIDDER_SERVER + '/delivery/matches.php?type=iframe', - }]; - } - } -} - -function getFlashVersion() { - var plugins, plugin, result; - - if (navigator.plugins && navigator.plugins.length > 0) { - plugins = navigator.plugins; - for (var i = 0; i < plugins.length && !result; i++) { - plugin = plugins[i]; - if (plugin.name.indexOf('Shockwave Flash') > -1) { - result = plugin.description.split('Shockwave Flash ')[1]; - } - } - } - return result || ''; -} - -/* Get parsed size from request size */ -function getSize(requestSizes) { - const parsed = {}; - const size = utils.parseSizesInput(requestSizes)[0]; - - if (typeof size !== 'string') { - return parsed; - } - - const parsedSize = size.toUpperCase().split('X'); - const width = parseInt(parsedSize[0], 10); - if (width) { - parsed.width = width; - } - - const height = parseInt(parsedSize[1], 10); - if (height) { - parsed.height = height; - } - - return parsed; -} - -function isSecure() { - return document.location.protocol === 'https:'; -} - -registerBidder(spec); diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 036bdfaebeb..a96abed9252 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { isArray, deepAccess, getOrigin, logError } from '../src/utils.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -9,6 +9,7 @@ const ENDPOINT_URL = 'creativecdn.com/bidder/prebid/bids'; const DEFAULT_CURRENCY_ARR = ['USD']; // NOTE - USD is the only supported currency right now; Hardcoded for bids const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE]; const TTL = 55; +const GVLID = 16; // Codes defined by OpenRTB Native Ads 1.1 specification export const OPENRTB = { @@ -36,6 +37,7 @@ export const OPENRTB = { export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + gvlid: GVLID, isBidRequestValid: function (bid) { return !!(includes(REGIONS, bid.params.region) && bid.params.publisherId); @@ -82,7 +84,7 @@ export const spec = { }, interpretResponse: function (serverResponse, originalRequest) { const responseBody = serverResponse.body; - if (!utils.isArray(responseBody)) { + if (!isArray(responseBody)) { return []; } @@ -145,7 +147,7 @@ function mapImpression(slot) { */ function mapBanner(slot) { if (slot.mediaType === 'banner' || - utils.deepAccess(slot, 'mediaTypes.banner') || + deepAccess(slot, 'mediaTypes.banner') || (!slot.mediaType && !slot.mediaTypes)) { var sizes = slot.sizes || slot.mediaTypes.banner.sizes; return { @@ -165,16 +167,26 @@ function mapBanner(slot) { * @returns {object} Site by OpenRTB 2.5 §3.2.13 */ function mapSite(slot, bidderRequest) { - const pubId = slot && slot.length > 0 - ? slot[0].params.publisherId - : 'unknown'; - return { + let pubId = 'unknown'; + let channel = null; + if (slot && slot.length > 0) { + pubId = slot[0].params.publisherId; + channel = slot[0].params.channel && + slot[0].params.channel + .toString() + .slice(0, 50); + } + let siteData = { publisher: { id: pubId.toString(), }, page: bidderRequest.refererInfo.referer, - name: utils.getOrigin() + name: getOrigin() + }; + if (channel) { + siteData.channel = channel; } + return siteData; } /** @@ -198,7 +210,7 @@ function mapSchain(schain) { return null; } if (!validateSchain(schain)) { - utils.logError('RTB House: required schain params missing'); + logError('RTB House: required schain params missing'); return null; } return schain; @@ -223,7 +235,7 @@ function validateSchain(schain) { * @returns {object} Request by OpenRTB Native Ads 1.1 §4 */ function mapNative(slot) { - if (slot.mediaType === 'native' || utils.deepAccess(slot, 'mediaTypes.native')) { + if (slot.mediaType === 'native' || deepAccess(slot, 'mediaTypes.native')) { return { request: { assets: mapNativeAssets(slot) @@ -238,7 +250,7 @@ function mapNative(slot) { * @returns {array} Request Assets by OpenRTB Native Ads 1.1 §4.2 */ function mapNativeAssets(slot) { - const params = slot.nativeParams || utils.deepAccess(slot, 'mediaTypes.native'); + const params = slot.nativeParams || deepAccess(slot, 'mediaTypes.native'); const assets = []; if (params.title) { assets.push({ diff --git a/modules/rtbsapeBidAdapter.js b/modules/rtbsapeBidAdapter.js index 8473ef4dbb3..d58b3a1f240 100644 --- a/modules/rtbsapeBidAdapter.js +++ b/modules/rtbsapeBidAdapter.js @@ -1,9 +1,8 @@ -import * as utils from '../src/utils.js'; +import { deepAccess, triggerPixel } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {OUTSTREAM} from '../src/video.js'; import {Renderer} from '../src/Renderer.js'; -import {triggerPixel} from '../src/utils.js'; const BIDDER_CODE = 'rtbsape'; const ENDPOINT = 'https://ssp-rtb.sape.ru/prebid'; @@ -64,30 +63,32 @@ export const spec = { let bids = {}; bidRequest.data.bids.forEach(bid => bids[bid.bidId] = bid); - return serverResponse.body.bids.map(bid => { - let requestBid = bids[bid.requestId]; - let context = utils.deepAccess(requestBid, 'mediaTypes.video.context'); + return serverResponse.body.bids + .filter(bid => typeof (bid.meta || {}).advertiserDomains !== 'undefined') + .map(bid => { + let requestBid = bids[bid.requestId]; + let context = deepAccess(requestBid, 'mediaTypes.video.context'); - if (context === OUTSTREAM && (bid.vastUrl || bid.vastXml)) { - let renderer = Renderer.install({ - id: bid.requestId, - url: RENDERER_SRC, - loaded: false - }); + if (context === OUTSTREAM && (bid.vastUrl || bid.vastXml)) { + let renderer = Renderer.install({ + id: bid.requestId, + url: RENDERER_SRC, + loaded: false + }); - let muted = utils.deepAccess(requestBid, 'params.video.playerMuted'); - if (typeof muted === 'undefined') { - muted = true; - } + let muted = deepAccess(requestBid, 'params.video.playerMuted'); + if (typeof muted === 'undefined') { + muted = true; + } - bid.playerMuted = muted; - bid.renderer = renderer + bid.playerMuted = muted; + bid.renderer = renderer - renderer.setRender(setOutstreamRenderer); - } + renderer.setRender(setOutstreamRenderer); + } - return bid; - }); + return bid; + }); }, /** diff --git a/modules/rtbsolutionsBidAdapter.js b/modules/rtbsolutionsBidAdapter.js deleted file mode 100644 index 244ab8a4eba..00000000000 --- a/modules/rtbsolutionsBidAdapter.js +++ /dev/null @@ -1,100 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; - -const BIDDER_CODE = 'rtbsolutions'; -const ENDPOINT_URL = 'https://dsp-eu-lb.rtbsolutions.pro/bid/hb'; - -export const spec = { - version: '1.0', - code: BIDDER_CODE, - aliases: ['rtbss'], // short code - nurls: {}, - isBidRequestValid: function(bid) { - return !!bid.params.blockId; - }, - buildRequests: function(validBidRequests, bidderRequest) { - let req = []; - - bidderRequest.bids.forEach(item => { - const width = item.sizes[0][0]; - const height = item.sizes[0][1]; - - let imp = { - referer: bidderRequest.refererInfo.referer, - ua: navigator.userAgent, - lang: this.getLanguage(), - domain: this.getDomain(), - width: width, - height: height, - type: 'banner', - }; - - if (item.params.s1 !== undefined) imp.s1 = item.params.s1; - if (item.params.s2 !== undefined) imp.s2 = item.params.s2; - if (item.params.s3 !== undefined) imp.s3 = item.params.s3; - if (item.params.s4 !== undefined) imp.s4 = item.params.s4; - - req.push({ - bid_id: item.bidId, - block_id: item.params.blockId, - ver: this.version, - imp - }); - }); - - return { - method: 'POST', - url: ENDPOINT_URL, - data: req, - options: { - contentType: 'application/json' - } - } - }, - interpretResponse: function(serverResponse, request) { - const bidResponses = []; - - serverResponse.body.forEach(item => { - this.nurls[item.bid_id] = item.nurl; - - const bidResponse = { - requestId: item.bid_id, - cpm: item.cpm, - width: item.width, - height: item.height, - creativeId: item.creative_id, - currency: item.currency, - netRevenue: true, - ttl: 360, - ad: item.ad, - }; - - bidResponses.push(bidResponse); - }); - - return bidResponses; - }, - onBidWon: function(bid) { - ajax(this.nurls[bid.requestId], null); - }, - - getLanguage() { - const language = navigator.language ? 'language' : 'userLanguage'; - const lang2 = navigator[language].split('-')[0]; - if (lang2.length === 2 || lang2.length === 3) { - return lang2; - } - return ''; - }, - getDomain() { - if (!utils.inIframe()) { - return window.location.hostname - } - let origins = window.document.location.ancestorOrigins; - if (origins && origins.length > 0) { - return origins[origins.length - 1] - } - } -}; -registerBidder(spec); diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index e235868f791..6c88d66999a 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -42,10 +42,10 @@ * @function? * @summary modify bid request data * @name RtdSubmodule#getBidRequestData - * @param {SubmoduleConfig} config - * @param {UserConsentData} userConsent * @param {Object} reqBidsConfigObj * @param {function} callback + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent */ /** @@ -98,6 +98,15 @@ * @param {UserConsentData} userConsent */ +/** + * @function? + * @summary on bid requested event + * @name RtdSubmodule#onBidRequestEvent + * @param {Object} data + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ + /** * @interface ModuleConfig */ @@ -143,7 +152,7 @@ import {config} from '../../src/config.js'; import {module} from '../../src/hook.js'; -import * as utils from '../../src/utils.js'; +import {logError, logInfo, logWarn} from '../../src/utils.js'; import events from '../../src/events.js'; import CONSTANTS from '../../src/constants.json'; import {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js'; @@ -164,17 +173,55 @@ let _dataProviders = []; let _userConsent; /** - * enable submodule in User ID + * Register a RTD submodule. + * * @param {RtdSubmodule} submodule + * @returns {function()} a de-registration function that will unregister the module when called. */ export function attachRealTimeDataProvider(submodule) { registeredSubModules.push(submodule); + return function detach() { + const idx = registeredSubModules.indexOf(submodule) + if (idx >= 0) { + registeredSubModules.splice(idx, 1); + initSubModules(); + } + } } +/** + * call each sub module event function by config order + */ +const setEventsListeners = (function () { + let registered = false; + return function setEventsListeners() { + if (!registered) { + Object.entries({ + [CONSTANTS.EVENTS.AUCTION_INIT]: ['onAuctionInitEvent'], + [CONSTANTS.EVENTS.AUCTION_END]: ['onAuctionEndEvent', getAdUnitTargeting], + [CONSTANTS.EVENTS.BID_RESPONSE]: ['onBidResponseEvent'], + [CONSTANTS.EVENTS.BID_REQUESTED]: ['onBidRequestEvent'] + }).forEach(([ev, [handler, preprocess]]) => { + events.on(ev, (args) => { + preprocess && preprocess(args); + subModules.forEach(sm => { + try { + sm[handler] && sm[handler](args, sm.config, _userConsent) + } catch (e) { + logError(`RTD provider '${sm.name}': error in '${handler}':`, e); + } + }); + }) + }); + registered = true; + } + } +})(); + export function init(config) { const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => { if (!realTimeData.dataProviders) { - utils.logError('missing parameters for real time module'); + logError('missing parameters for real time module'); return; } confListener(); // unsubscribe config listener @@ -209,22 +256,7 @@ function initSubModules() { } }); subModules = subModulesByOrder; -} - -/** - * call each sub module event function by config order - */ -function setEventsListeners() { - events.on(CONSTANTS.EVENTS.AUCTION_INIT, (args) => { - subModules.forEach(sm => { sm.onAuctionInitEvent && sm.onAuctionInitEvent(args, sm.config, _userConsent) }) - }); - events.on(CONSTANTS.EVENTS.AUCTION_END, (args) => { - getAdUnitTargeting(args); - subModules.forEach(sm => { sm.onAuctionEndEvent && sm.onAuctionEndEvent(args, sm.config, _userConsent) }) - }); - events.on(CONSTANTS.EVENTS.BID_RESPONSE, (args) => { - subModules.forEach(sm => { sm.onBidResponseEvent && sm.onBidResponseEvent(args, sm.config, _userConsent) }) - }); + logInfo(`Real time data module enabled, using submodules: ${subModules.map((m) => m.name).join(', ')}`); } /** @@ -259,18 +291,12 @@ export function setBidRequestsData(fn, reqBidsConfigObj) { return exitHook(); } - if (shouldDelayAuction) { - waitTimeout = setTimeout(exitHook, _moduleConfig.auctionDelay); - } + waitTimeout = setTimeout(exitHook, shouldDelayAuction ? _moduleConfig.auctionDelay : 0); relevantSubModules.forEach(sm => { sm.getBidRequestData(reqBidsConfigObj, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent) }); - if (!shouldDelayAuction) { - return exitHook(); - } - function onGetBidRequestDataCallback() { if (isDone) { return; @@ -278,12 +304,15 @@ export function setBidRequestsData(fn, reqBidsConfigObj) { if (this.config && this.config.waitForIt) { callbacksExpected--; } - if (callbacksExpected <= 0) { - return exitHook(); + if (callbacksExpected === 0) { + setTimeout(exitHook, 0); } } function exitHook() { + if (isDone) { + return; + } isDone = true; clearTimeout(waitTimeout); fn.call(this, reqBidsConfigObj); @@ -314,7 +343,7 @@ export function getAdUnitTargeting(auction) { if (smTargeting && typeof smTargeting === 'object') { targeting.push(smTargeting); } else { - utils.logWarn('invalid getTargetingData response for sub module', relevantSubModules[i].name); + logWarn('invalid getTargetingData response for sub module', relevantSubModules[i].name); } } // place data on auction adUnits diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 3237facb2e7..ed24598e986 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -1,9 +1,9 @@ +import { generateUUID, mergeDeep, deepAccess, parseUrl, logError, pick, isEmpty, logWarn, debugTurnedOn, parseQS, getWindowLocation, isAdUnitCodeMatchingSlot, isNumber, isGptPubadsDefined, _each, deepSetValue } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; -import * as utils from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -43,7 +43,7 @@ const { } = CONSTANTS; let serverConfig; -config.getConfig('s2sConfig', ({s2sConfig}) => { +config.getConfig('s2sConfig', ({ s2sConfig }) => { serverConfig = s2sConfig; }); @@ -60,23 +60,23 @@ const cache = { const BID_REJECTED_IPF = 'rejected-ipf'; export let rubiConf = { - pvid: utils.generateUUID().slice(0, 8), + pvid: generateUUID().slice(0, 8), analyticsEventDelay: 0 }; // we are saving these as global to this module so that if a pub accidentally overwrites the entire // rubicon object, then we do not lose other data config.getConfig('rubicon', config => { - utils.mergeDeep(rubiConf, config.rubicon); - if (utils.deepAccess(config, 'rubicon.updatePageView') === true) { - rubiConf.pvid = utils.generateUUID().slice(0, 8) + mergeDeep(rubiConf, config.rubicon); + if (deepAccess(config, 'rubicon.updatePageView') === true) { + rubiConf.pvid = generateUUID().slice(0, 8) } }); export function getHostNameFromReferer(referer) { try { - rubiconAdapter.referrerHostname = utils.parseUrl(referer, {noDecodeWholeURL: true}).hostname; + rubiconAdapter.referrerHostname = parseUrl(referer, { noDecodeWholeURL: true }).hostname; } catch (e) { - utils.logError('Rubicon Analytics: Unable to parse hostname from supplied url: ', referer, e); + logError('Rubicon Analytics: Unable to parse hostname from supplied url: ', referer, e); rubiconAdapter.referrerHostname = ''; } return rubiconAdapter.referrerHostname @@ -90,7 +90,7 @@ function stringProperties(obj) { } else if (typeof value !== 'string') { value = String(value); } - newObj[prop] = value; + newObj[prop] = value || undefined; return newObj; }, {}); } @@ -115,12 +115,12 @@ function formatSource(src) { return src.toLowerCase(); } -function sendMessage(auctionId, bidWonId) { +function sendMessage(auctionId, bidWonId, trigger) { function formatBid(bid) { - return utils.pick(bid, [ + return pick(bid, [ 'bidder', 'bidderDetail', - 'bidId', bidId => utils.deepAccess(bid, 'bidResponse.pbsBidId') || utils.deepAccess(bid, 'bidResponse.seatBidId') || bidId, + 'bidId', bidId => deepAccess(bid, 'bidResponse.pbsBidId') || deepAccess(bid, 'bidResponse.seatBidId') || bidId, 'status', 'error', 'source', (source, bid) => { @@ -133,7 +133,7 @@ function sendMessage(auctionId, bidWonId) { 'clientLatencyMillis', 'serverLatencyMillis', 'params', - 'bidResponse', bidResponse => bidResponse ? utils.pick(bidResponse, [ + 'bidResponse', bidResponse => bidResponse ? pick(bidResponse, [ 'bidPriceUSD', 'dealId', 'dimensions', @@ -146,13 +146,13 @@ function sendMessage(auctionId, bidWonId) { ]); } function formatBidWon(bid) { - return Object.assign(formatBid(bid), utils.pick(bid.adUnit, [ + return Object.assign(formatBid(bid), pick(bid.adUnit, [ 'adUnitCode', 'transactionId', 'videoAdFormat', () => bid.videoAdFormat, 'mediaTypes' ]), { - adserverTargeting: stringProperties(cache.targeting[bid.adUnit.adUnitCode] || {}), + adserverTargeting: !isEmpty(cache.targeting[bid.adUnit.adUnitCode]) ? stringProperties(cache.targeting[bid.adUnit.adUnitCode]) : undefined, bidwonStatus: 'success', // hard-coded for now accountId, siteId: bid.siteId, @@ -163,7 +163,12 @@ function sendMessage(auctionId, bidWonId) { let auctionCache = cache.auctions[auctionId]; let referrer = config.getConfig('pageUrl') || (auctionCache && auctionCache.referrer); let message = { - eventTimeMillis: Date.now(), + timestamps: { + prebidLoaded: rubiconAdapter.MODULE_INITIALIZED_TIME, + auctionEnded: auctionCache.endTs, + eventTime: Date.now() + }, + trigger, integration: rubiConf.int_type || DEFAULT_INTEGRATION, version: '$prebid.version$', referrerUri: referrer, @@ -182,13 +187,13 @@ function sendMessage(auctionId, bidWonId) { let bid = auctionCache.bids[bidId]; let adUnit = adUnits[bid.adUnit.adUnitCode]; if (!adUnit) { - adUnit = adUnits[bid.adUnit.adUnitCode] = utils.pick(bid.adUnit, [ + adUnit = adUnits[bid.adUnit.adUnitCode] = pick(bid.adUnit, [ 'adUnitCode', 'transactionId', 'mediaTypes', 'dimensions', - 'adserverTargeting', () => stringProperties(cache.targeting[bid.adUnit.adUnitCode] || {}), - 'gam', + 'adserverTargeting', () => !isEmpty(cache.targeting[bid.adUnit.adUnitCode]) ? stringProperties(cache.targeting[bid.adUnit.adUnitCode]) : undefined, + 'gam', gam => !isEmpty(gam) ? gam : undefined, 'pbAdSlot', 'pattern' ]); @@ -198,10 +203,10 @@ function sendMessage(auctionId, bidWonId) { // Add site and zone id if not there and if we found a rubicon bidder if ((!adUnit.siteId || !adUnit.zoneId) && rubiconAliases.indexOf(bid.bidder) !== -1) { - if (utils.deepAccess(bid, 'params.accountId') == accountId) { + if (deepAccess(bid, 'params.accountId') == accountId) { adUnit.accountId = parseInt(accountId); - adUnit.siteId = parseInt(utils.deepAccess(bid, 'params.siteId')); - adUnit.zoneId = parseInt(utils.deepAccess(bid, 'params.zoneId')); + adUnit.siteId = parseInt(deepAccess(bid, 'params.siteId')); + adUnit.zoneId = parseInt(deepAccess(bid, 'params.zoneId')); } } @@ -224,11 +229,14 @@ function sendMessage(auctionId, bidWonId) { // This allows the bidWon events to have these params even in the case of a delayed render Object.keys(auctionCache.bids).forEach(function (bidId) { let adCode = auctionCache.bids[bidId].adUnit.adUnitCode; - Object.assign(auctionCache.bids[bidId], utils.pick(adUnitMap[adCode], ['accountId', 'siteId', 'zoneId'])); + Object.assign(auctionCache.bids[bidId], pick(adUnitMap[adCode], ['accountId', 'siteId', 'zoneId'])); }); let auction = { clientTimeoutMillis: auctionCache.timeout, + auctionStart: auctionCache.timestamp, + auctionEnd: auctionCache.endTs, + bidderOrder: auctionCache.bidderOrder, samplingFactor, accountId, adUnits: Object.keys(adUnitMap).map(i => adUnitMap[i]), @@ -238,20 +246,20 @@ function sendMessage(auctionId, bidWonId) { // pick our of top level floor data we want to send! if (auctionCache.floorData) { if (auctionCache.floorData.location === 'noData') { - auction.floors = utils.pick(auctionCache.floorData, [ + auction.floors = pick(auctionCache.floorData, [ 'location', 'fetchStatus', 'floorProvider as provider' ]); } else { - auction.floors = utils.pick(auctionCache.floorData, [ + auction.floors = pick(auctionCache.floorData, [ 'location', 'modelVersion as modelName', 'modelWeight', 'modelTimestamp', 'skipped', - 'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'), - 'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'), + 'enforcement', () => deepAccess(auctionCache.floorData, 'enforcements.enforceJS'), + 'dealsEnforced', () => deepAccess(auctionCache.floorData, 'enforcements.floorDeals'), 'skipRate', 'fetchStatus', 'floorMin', @@ -262,7 +270,7 @@ function sendMessage(auctionId, bidWonId) { // gather gdpr info if (auctionCache.gdprConsent) { - auction.gdpr = utils.pick(auctionCache.gdprConsent, [ + auction.gdpr = pick(auctionCache.gdprConsent, [ 'gdprApplies as applies', 'consentString', 'apiVersion as version' @@ -271,13 +279,13 @@ function sendMessage(auctionId, bidWonId) { // gather session info if (auctionCache.session) { - message.session = utils.pick(auctionCache.session, [ + message.session = pick(auctionCache.session, [ 'id', 'pvid', 'start', 'expires' ]); - if (!utils.isEmpty(auctionCache.session.fpkvs)) { + if (!isEmpty(auctionCache.session.fpkvs)) { message.fpkvs = Object.keys(auctionCache.session.fpkvs).map(key => { return { key, value: auctionCache.session.fpkvs[key] }; }); @@ -288,6 +296,10 @@ function sendMessage(auctionId, bidWonId) { auction.serverTimeoutMillis = serverConfig.timeout; } + if (auctionCache.userIds.length) { + auction.user = { ids: auctionCache.userIds }; + } + message.auctions = [auction]; let bidsWon = Object.keys(auctionCache.bidsWon).reduce((memo, adUnitCode) => { @@ -319,11 +331,15 @@ function sendMessage(auctionId, bidWonId) { ); } +function adUnitIsOnlyInstream(adUnit) { + return adUnit.mediaTypes && Object.keys(adUnit.mediaTypes).length === 1 && deepAccess(adUnit, 'mediaTypes.video.context') === 'instream'; +} + function getBidPrice(bid) { // get the cpm from bidResponse let cpm; let currency; - if (bid.status === BID_REJECTED && utils.deepAccess(bid, 'floorData.cpmAfterAdjustments')) { + if (bid.status === BID_REJECTED && deepAccess(bid, 'floorData.cpmAfterAdjustments')) { // if bid was rejected and bid.floorData.cpmAfterAdjustments use it cpm = bid.floorData.cpmAfterAdjustments; currency = bid.floorData.floorCurrency; @@ -343,7 +359,7 @@ function getBidPrice(bid) { try { return Number(prebidGlobal.convertCurrency(cpm, currency, 'USD')); } catch (err) { - utils.logWarn('Rubicon Analytics Adapter: Could not determine the bidPriceUSD of the bid ', bid); + logWarn('Rubicon Analytics Adapter: Could not determine the bidPriceUSD of the bid ', bid); } } @@ -354,7 +370,7 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { if (previousBidResponse && previousBidResponse.bidPriceUSD > responsePrice) { return previousBidResponse; } - return utils.pick(bid, [ + return pick(bid, [ 'bidPriceUSD', () => responsePrice, 'dealId', 'status', @@ -362,17 +378,18 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { 'dimensions', () => { const width = bid.width || bid.playerWidth; const height = bid.height || bid.playerHeight; - return (width && height) ? {width, height} : undefined; + return (width && height) ? { width, height } : undefined; }, // Handling use case where pbs sends back 0 or '0' bidIds - 'pbsBidId', pbsBidId => pbsBidId == 0 ? utils.generateUUID() : pbsBidId, - 'seatBidId', seatBidId => seatBidId == 0 ? utils.generateUUID() : seatBidId, - 'floorValue', () => utils.deepAccess(bid, 'floorData.floorValue'), - 'floorRuleValue', () => utils.deepAccess(bid, 'floorData.floorRuleValue'), - 'floorRule', () => utils.debugTurnedOn() ? utils.deepAccess(bid, 'floorData.floorRule') : undefined, + 'pbsBidId', pbsBidId => pbsBidId == 0 ? generateUUID() : pbsBidId, + 'seatBidId', seatBidId => seatBidId == 0 ? generateUUID() : seatBidId, + 'floorValue', () => deepAccess(bid, 'floorData.floorValue'), + 'floorRuleValue', () => deepAccess(bid, 'floorData.floorRuleValue'), + 'floorRule', () => debugTurnedOn() ? deepAccess(bid, 'floorData.floorRule') : undefined, 'adomains', () => { - let adomains = utils.deepAccess(bid, 'meta.advertiserDomains'); - return Array.isArray(adomains) && adomains.length > 0 ? adomains.slice(0, 10) : undefined + const adomains = deepAccess(bid, 'meta.advertiserDomains'); + const validAdomains = Array.isArray(adomains) && adomains.filter(domain => typeof domain === 'string'); + return validAdomains && validAdomains.length > 0 ? validAdomains.slice(0, 10) : undefined } ]); } @@ -384,7 +401,7 @@ function getUtmParams() { let search; try { - search = utils.parseQS(utils.getWindowLocation().search); + search = parseQS(getWindowLocation().search); } catch (e) { search = {}; } @@ -432,7 +449,7 @@ function getRpaCookie() { try { return JSON.parse(window.atob(encodedCookie)); } catch (e) { - utils.logError(`Rubicon Analytics: Unable to decode ${COOKIE_NAME} value: `, e); + logError(`Rubicon Analytics: Unable to decode ${COOKIE_NAME} value: `, e); } } return {}; @@ -442,7 +459,7 @@ function setRpaCookie(decodedCookie) { try { storage.setDataInLocalStorage(COOKIE_NAME, window.btoa(JSON.stringify(decodedCookie))); } catch (e) { - utils.logError(`Rubicon Analytics: Unable to encode ${COOKIE_NAME} value: `, e); + logError(`Rubicon Analytics: Unable to encode ${COOKIE_NAME} value: `, e); } } @@ -455,7 +472,7 @@ function updateRpaCookie() { decodedRpaCookie.expires < currentTime ) { decodedRpaCookie = { - id: utils.generateUUID(), + id: generateUUID(), start: currentTime, expires: currentTime + END_EXPIRE_TIME, // six hours later, } @@ -463,7 +480,7 @@ function updateRpaCookie() { // possible that decodedRpaCookie is undefined, and if it is, we probably are blocked by storage or some other exception if (Object.keys(decodedRpaCookie).length) { decodedRpaCookie.lastSeen = currentTime; - decodedRpaCookie.fpkvs = {...decodedRpaCookie.fpkvs, ...getFpkvs()}; + decodedRpaCookie.fpkvs = { ...decodedRpaCookie.fpkvs, ...getFpkvs() }; decodedRpaCookie.pvid = rubiConf.pvid; setRpaCookie(decodedRpaCookie) } @@ -472,7 +489,7 @@ function updateRpaCookie() { function subscribeToGamSlots() { window.googletag.pubads().addEventListener('slotRenderEnded', event => { - const isMatchingAdSlot = utils.isAdUnitCodeMatchingSlot(event.slot); + const isMatchingAdSlot = isAdUnitCodeMatchingSlot(event.slot); // loop through auctions and adUnits and mark the info Object.keys(cache.auctions).forEach(auctionId => { (Object.keys(cache.auctions[auctionId].bids) || []).forEach(bidId => { @@ -482,12 +499,12 @@ function subscribeToGamSlots() { // mark this adUnit as having been rendered by gam cache.auctions[auctionId].gamHasRendered[bid.adUnit.adUnitCode] = true; - bid.adUnit.gam = utils.pick(event, [ + bid.adUnit.gam = pick(event, [ // these come in as `null` from Gpt, which when stringified does not get removed // so set explicitly to undefined when not a number - 'advertiserId', advertiserId => utils.isNumber(advertiserId) ? advertiserId : undefined, - 'creativeId', creativeId => utils.isNumber(event.sourceAgnosticCreativeId) ? event.sourceAgnosticCreativeId : utils.isNumber(creativeId) ? creativeId : undefined, - 'lineItemId', lineItemId => utils.isNumber(event.sourceAgnosticLineItemId) ? event.sourceAgnosticLineItemId : utils.isNumber(lineItemId) ? lineItemId : undefined, + 'advertiserId', advertiserId => isNumber(advertiserId) ? advertiserId : undefined, + 'creativeId', creativeId => isNumber(event.sourceAgnosticCreativeId) ? event.sourceAgnosticCreativeId : isNumber(creativeId) ? creativeId : undefined, + 'lineItemId', lineItemId => isNumber(event.sourceAgnosticLineItemId) ? event.sourceAgnosticLineItemId : isNumber(lineItemId) ? lineItemId : undefined, 'adSlot', () => event.slot.getAdUnitPath(), 'isSlotEmpty', () => event.isEmpty || undefined ]); @@ -498,17 +515,18 @@ function subscribeToGamSlots() { clearTimeout(cache.timeouts[auctionId]); delete cache.timeouts[auctionId]; if (rubiConf.analyticsEventDelay > 0) { - setTimeout(() => sendMessage.call(rubiconAdapter, auctionId), rubiConf.analyticsEventDelay) + setTimeout(() => sendMessage.call(rubiconAdapter, auctionId, undefined, 'delayedGam'), rubiConf.analyticsEventDelay) } else { - sendMessage.call(rubiconAdapter, auctionId) + sendMessage.call(rubiconAdapter, auctionId, undefined, 'gam') } } }); }); } -let baseAdapter = adapter({analyticsType: 'endpoint'}); +let baseAdapter = adapter({ analyticsType: 'endpoint' }); let rubiconAdapter = Object.assign({}, baseAdapter, { + MODULE_INITIALIZED_TIME: Date.now(), referrerHostname: '', enableAnalytics(config = {}) { let error = false; @@ -521,7 +539,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { if (config.options.endpoint) { this.getUrl = () => config.options.endpoint; } else { - utils.logError('required endpoint missing from rubicon analytics'); + logError('required endpoint missing from rubicon analytics'); error = true; } if (typeof config.options.sampling !== 'undefined') { @@ -529,7 +547,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { } if (typeof config.options.samplingFactor !== 'undefined') { if (typeof config.options.sampling !== 'undefined') { - utils.logWarn('Both options.samplingFactor and options.sampling enabled in rubicon analytics, defaulting to samplingFactor'); + logWarn('Both options.samplingFactor and options.sampling enabled in rubicon analytics, defaulting to samplingFactor'); } samplingFactor = parseFloat(config.options.samplingFactor); config.options.sampling = 1 / samplingFactor; @@ -539,10 +557,10 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { let validSamplingFactors = [1, 10, 20, 40, 100]; if (validSamplingFactors.indexOf(samplingFactor) === -1) { error = true; - utils.logError('invalid samplingFactor for rubicon analytics: ' + samplingFactor + ', must be one of ' + validSamplingFactors.join(', ')); + logError('invalid samplingFactor for rubicon analytics: ' + samplingFactor + ', must be one of ' + validSamplingFactors.join(', ')); } else if (!accountId) { error = true; - utils.logError('required accountId missing for rubicon analytics'); + logError('required accountId missing for rubicon analytics'); } if (!error) { @@ -556,49 +574,54 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { cache.gpt.registered = false; baseAdapter.disableAnalytics.apply(this, arguments); }, - track({eventType, args}) { + track({ eventType, args }) { switch (eventType) { case AUCTION_INIT: // set the rubicon aliases setRubiconAliases(adapterManager.aliasRegistry); - let cacheEntry = utils.pick(args, [ + let cacheEntry = pick(args, [ 'timestamp', 'timeout' ]); cacheEntry.bids = {}; cacheEntry.bidsWon = {}; cacheEntry.gamHasRendered = {}; - cacheEntry.referrer = utils.deepAccess(args, 'bidderRequests.0.refererInfo.referer'); - const floorData = utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData'); + cacheEntry.bidderOrder = []; + cacheEntry.referrer = deepAccess(args, 'bidderRequests.0.refererInfo.referer'); + const floorData = deepAccess(args, 'bidderRequests.0.bids.0.floorData'); if (floorData) { - cacheEntry.floorData = {...floorData}; + cacheEntry.floorData = { ...floorData }; } - cacheEntry.gdprConsent = utils.deepAccess(args, 'bidderRequests.0.gdprConsent'); + cacheEntry.gdprConsent = deepAccess(args, 'bidderRequests.0.gdprConsent'); cacheEntry.session = storage.localStorageIsEnabled() && updateRpaCookie(); + cacheEntry.userIds = Object.keys(deepAccess(args, 'bidderRequests.0.bids.0.userId', {})).map(id => { + return { provider: id, hasId: true } + }); cache.auctions[args.auctionId] = cacheEntry; // register to listen to gpt events if not done yet - if (!cache.gpt.registered && utils.isGptPubadsDefined()) { + if (!cache.gpt.registered && isGptPubadsDefined()) { subscribeToGamSlots(); cache.gpt.registered = true; } else if (!cache.gpt.registered) { cache.gpt.registered = true; window.googletag = window.googletag || {}; window.googletag.cmd = window.googletag.cmd || []; - window.googletag.cmd.push(function() { + window.googletag.cmd.push(function () { subscribeToGamSlots(); }); } break; case BID_REQUESTED: + cache.auctions[args.auctionId].bidderOrder.push(args.bidderCode); Object.assign(cache.auctions[args.auctionId].bids, args.bids.reduce((memo, bid) => { // mark adUnits we expect bidWon events for cache.auctions[args.auctionId].bidsWon[bid.adUnitCode] = false; - if (rubiConf.waitForGamSlots) { + if (rubiConf.waitForGamSlots && !adUnitIsOnlyInstream(bid)) { cache.auctions[args.auctionId].gamHasRendered[bid.adUnitCode] = false; } - memo[bid.bidId] = utils.pick(bid, [ + memo[bid.bidId] = pick(bid, [ 'bidder', bidder => bidder.toLowerCase(), 'bidId', 'status', () => 'no-bid', // default a bid to no-bid until response is recieved or bid is timed out @@ -607,7 +630,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { switch (bid.bidder) { // specify bidder params we want here case 'rubicon': - return utils.pick(params, [ + return pick(params, [ 'accountId', 'siteId', 'zoneId' @@ -623,9 +646,9 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { 204: 'mid-roll', 205: 'post-roll', 207: 'vertical' - })[utils.deepAccess(bid, 'params.video.size_id')]; + })[deepAccess(bid, 'params.video.size_id')]; } else { - let startdelay = parseInt(utils.deepAccess(bid, 'params.video.startdelay'), 10); + let startdelay = parseInt(deepAccess(bid, 'params.video.startdelay'), 10); if (!isNaN(startdelay)) { if (startdelay > 0) { return 'mid-roll'; @@ -638,7 +661,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { } } }, - 'adUnit', () => utils.pick(bid, [ + 'adUnit', () => pick(bid, [ 'adUnitCode', 'transactionId', 'sizes as dimensions', sizes => sizes.map(sizeToDimensions), @@ -652,7 +675,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { if (typeof types === 'object') { if (!bid.sizes) { bid.dimensions = []; - utils._each(types, (type) => + _each(types, (type) => bid.dimensions = bid.dimensions.concat( type.sizes.map(sizeToDimensions) ) @@ -663,12 +686,12 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { return ['banner']; }, 'gam', () => { - if (utils.deepAccess(bid, 'ortb2Imp.ext.data.adserver.name') === 'gam') { - return {adSlot: bid.ortb2Imp.ext.data.adserver.adSlot} + if (deepAccess(bid, 'ortb2Imp.ext.data.adserver.name') === 'gam') { + return { adSlot: bid.ortb2Imp.ext.data.adserver.adslot } } }, - 'pbAdSlot', () => utils.deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'), - 'pattern', () => utils.deepAccess(bid, 'ortb2Imp.ext.data.aupname') + 'pbAdSlot', () => deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'), + 'pattern', () => deepAccess(bid, 'ortb2Imp.ext.data.aupname') ]) ]); return memo; @@ -678,22 +701,22 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { let auctionEntry = cache.auctions[args.auctionId]; if (!auctionEntry.bids[args.requestId] && args.originalRequestId) { - auctionEntry.bids[args.requestId] = {...auctionEntry.bids[args.originalRequestId]}; + auctionEntry.bids[args.requestId] = { ...auctionEntry.bids[args.originalRequestId] }; auctionEntry.bids[args.requestId].bidId = args.requestId; auctionEntry.bids[args.requestId].bidderDetail = args.targetingBidder; } let bid = auctionEntry.bids[args.requestId]; // If floor resolved gptSlot but we have not yet, then update the adUnit to have the adSlot name - if (!utils.deepAccess(bid, 'adUnit.gam.adSlot') && utils.deepAccess(args, 'floorData.matchedFields.gptSlot')) { - utils.deepSetValue(bid, 'adUnit.gam.adSlot', args.floorData.matchedFields.gptSlot); + if (!deepAccess(bid, 'adUnit.gam.adSlot') && deepAccess(args, 'floorData.matchedFields.gptSlot')) { + deepSetValue(bid, 'adUnit.gam.adSlot', args.floorData.matchedFields.gptSlot); } // if we have not set enforcements yet set it - if (!utils.deepAccess(auctionEntry, 'floorData.enforcements') && utils.deepAccess(args, 'floorData.enforcements')) { - auctionEntry.floorData.enforcements = {...args.floorData.enforcements}; + if (!deepAccess(auctionEntry, 'floorData.enforcements') && deepAccess(args, 'floorData.enforcements')) { + auctionEntry.floorData.enforcements = { ...args.floorData.enforcements }; } if (!bid) { - utils.logError('Rubicon Anlytics Adapter Error: Could not find associated bid request for bid response with requestId: ', args.requestId); + logError('Rubicon Anlytics Adapter Error: Could not find associated bid request for bid response with requestId: ', args.requestId); break; } bid.source = formatSource(bid.source || args.source); @@ -716,7 +739,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { bid.bidResponse = parseBidResponse(args, bid.bidResponse); break; case BIDDER_DONE: - const serverError = utils.deepAccess(args, 'serverErrors.0'); + const serverError = deepAccess(args, 'serverErrors.0'); const serverResponseTimeMs = args.serverResponseTimeMs; args.bids.forEach(bid => { let cachedBid = cache.auctions[bid.auctionId].bids[bid.bidId || bid.requestId]; @@ -750,7 +773,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { // check if this BID_WON missed the boat, if so send by itself if (auctionCache.sent === true) { - sendMessage.call(this, args.auctionId, args.requestId); + sendMessage.call(this, args.auctionId, args.requestId, 'soloBidWon'); } else if (!rubiConf.waitForGamSlots && Object.keys(auctionCache.bidsWon).reduce((memo, adUnitCode) => { // only send if we've received bidWon events for all adUnits in auction memo = memo && auctionCache.bidsWon[adUnitCode]; @@ -759,14 +782,23 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { clearTimeout(cache.timeouts[args.auctionId]); delete cache.timeouts[args.auctionId]; - sendMessage.call(this, args.auctionId); + sendMessage.call(this, args.auctionId, undefined, 'allBidWons'); } break; case AUCTION_END: - // start timer to send batched payload just in case we don't hear any BID_WON events - cache.timeouts[args.auctionId] = setTimeout(() => { - sendMessage.call(this, args.auctionId); - }, rubiConf.analyticsBatchTimeout || SEND_TIMEOUT); + // see how long it takes for the payload to come fire + cache.auctions[args.auctionId].endTs = Date.now(); + + const isOnlyInstreamAuction = args.adUnits && args.adUnits.every(adUnit => adUnitIsOnlyInstream(adUnit)); + // If only instream, do not wait around, just send payload + if (isOnlyInstreamAuction) { + sendMessage.call(this, args.auctionId, undefined, 'instreamAuction'); + } else { + // start timer to send batched payload just in case we don't hear any BID_WON events + cache.timeouts[args.auctionId] = setTimeout(() => { + sendMessage.call(this, args.auctionId, undefined, 'auctionEnd'); + }, rubiConf.analyticsBatchTimeout || SEND_TIMEOUT); + } break; case BID_TIMEOUT: args.forEach(badBid => { @@ -777,7 +809,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { bid.status = 'error'; bid.error = { code: 'timeout-error', - message: 'marked by prebid.js as timeout' // will help us diff if timeout was set by PBS or PBJS + description: 'prebid.js timeout' // will help us diff if timeout was set by PBS or PBJS }; } }); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 9fb112eb765..9f3ba28a27c 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { mergeDeep, _each, logError, deepAccess, deepSetValue, isStr, isNumber, logWarn, convertTypes, isArray, parseSizesInput, logMessage, formatQS } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -8,14 +8,14 @@ import { getGlobal } from '../src/prebidGlobal.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; -const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.0.0.js'; +const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.2.1.js'; // renderer code at https://github.com/rubicon-project/apex2 let rubiConf = {}; // we are saving these as global to this module so that if a pub accidentally overwrites the entire // rubicon object, then we do not lose other data config.getConfig('rubicon', config => { - utils.mergeDeep(rubiConf, config.rubicon); + mergeDeep(rubiConf, config.rubicon); }); const GVLID = 52; @@ -110,9 +110,19 @@ var sizeMap = { 548: '500x1000', 550: '980x480', 552: '300x200', - 558: '640x640' + 558: '640x640', + 562: '300x431', + 564: '320x431', + 566: '320x300', + 568: '300x150', + 570: '300x125', + 572: '250x350', + 574: '620x891', + 576: '610x877', + 578: '980x552', + 580: '505x656' }; -utils._each(sizeMap, (item, key) => sizeMap[item] = key); +_each(sizeMap, (item, key) => sizeMap[item] = key); export const spec = { code: 'rubicon', @@ -130,7 +140,7 @@ export const spec = { for (let i = 0, props = ['accountId', 'siteId', 'zoneId']; i < props.length; i++) { bid.params[props[i]] = parseInt(bid.params[props[i]]) if (isNaN(bid.params[props[i]])) { - utils.logError('Rubicon: wrong format of accountId or siteId or zoneId.') + logError('Rubicon: wrong format of accountId or siteId or zoneId.') return false } } @@ -170,10 +180,14 @@ export const spec = { ext: { [bidRequest.bidder]: bidRequest.params }, - video: utils.deepAccess(bidRequest, 'mediaTypes.video') || {} + video: deepAccess(bidRequest, 'mediaTypes.video') || {} }], ext: { prebid: { + channel: { + name: 'pbjs', + version: $$PREBID_GLOBAL$$.version + }, cache: { vastxml: { returnCreative: rubiConf.returnVast === true @@ -203,7 +217,7 @@ export const spec = { let modules = (getGlobal()).installedModules; if (modules && (!modules.length || modules.indexOf('rubiconAnalyticsAdapter') !== -1)) { - utils.deepSetValue(data, 'ext.prebid.analytics', {'rubicon': {'client-analytics': true}}); + deepSetValue(data, 'ext.prebid.analytics', {'rubicon': {'client-analytics': true}}); } let bidFloor; @@ -216,11 +230,11 @@ export const spec = { size: parseSizes(bidRequest, 'video') }); } catch (e) { - utils.logError('Rubicon: getFloor threw an error: ', e); + logError('Rubicon: getFloor threw an error: ', e); } bidFloor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined; } else { - bidFloor = parseFloat(utils.deepAccess(bidRequest, 'params.floor')); + bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); } if (!isNaN(bidFloor)) { data.imp[0].bidfloor = bidFloor; @@ -239,36 +253,36 @@ export const spec = { gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; } - utils.deepSetValue(data, 'regs.ext.gdpr', gdprApplies); - utils.deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(data, 'regs.ext.gdpr', gdprApplies); + deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } if (bidderRequest.uspConsent) { - utils.deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - const eids = utils.deepAccess(bidderRequest, 'bids.0.userIdAsEids'); + const eids = deepAccess(bidderRequest, 'bids.0.userIdAsEids'); if (eids && eids.length) { - utils.deepSetValue(data, 'user.ext.eids', eids); + deepSetValue(data, 'user.ext.eids', eids); } // set user.id value from config value const configUserId = config.getConfig('user.id'); if (configUserId) { - utils.deepSetValue(data, 'user.id', configUserId); + deepSetValue(data, 'user.id', configUserId); } if (config.getConfig('coppa') === true) { - utils.deepSetValue(data, 'regs.coppa', 1); + deepSetValue(data, 'regs.coppa', 1); } if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) { - utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); + deepSetValue(data, 'source.ext.schain', bidRequest.schain); } const multibid = config.getConfig('multibid'); if (multibid) { - utils.deepSetValue(data, 'ext.prebid.multibid', multibid.reduce((result, i) => { + deepSetValue(data, 'ext.prebid.multibid', multibid.reduce((result, i) => { let obj = {}; Object.keys(i).forEach(key => { @@ -285,11 +299,11 @@ export const spec = { // if storedAuctionResponse has been set, pass SRID if (bidRequest.storedAuctionResponse) { - utils.deepSetValue(data.imp[0], 'ext.prebid.storedauctionresponse.id', bidRequest.storedAuctionResponse.toString()); + deepSetValue(data.imp[0], 'ext.prebid.storedauctionresponse.id', bidRequest.storedAuctionResponse.toString()); } // set ext.prebid.auctiontimestamp using auction time - utils.deepSetValue(data.imp[0], 'ext.prebid.auctiontimestamp', bidderRequest.auctionStart); + deepSetValue(data.imp[0], 'ext.prebid.auctiontimestamp', bidderRequest.auctionStart); return { method: 'POST', @@ -308,14 +322,14 @@ export const spec = { url: `https://${rubiConf.bannerHost || 'fastlane'}.rubiconproject.com/a/api/fastlane.json`, data: spec.getOrderedParams(bidParams).reduce((paramString, key) => { const propValue = bidParams[key]; - return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${encodeParam(key, propValue)}&` : paramString; + return ((isStr(propValue) && propValue !== '') || isNumber(propValue)) ? `${paramString}${encodeParam(key, propValue)}&` : paramString; }, '') + `slots=1&rand=${Math.random()}`, bidRequest }; })); } else { // single request requires bids to be grouped by site id into a single request - // note: utils.groupBy wasn't used because deep property access was needed + // note: groupBy wasn't used because deep property access was needed const nonVideoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'banner'); const groupedBidRequests = nonVideoRequests.reduce((groupedBids, bid) => { (groupedBids[bid.params['siteId']] = groupedBids[bid.params['siteId']] || []).push(bid); @@ -339,7 +353,7 @@ export const spec = { url: `https://${rubiConf.bannerHost || 'fastlane'}.rubiconproject.com/a/api/fastlane.json`, data: spec.getOrderedParams(combinedSlotParams).reduce((paramString, key) => { const propValue = combinedSlotParams[key]; - return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${encodeParam(key, propValue)}&` : paramString; + return ((isStr(propValue) && propValue !== '') || isNumber(propValue)) ? `${paramString}${encodeParam(key, propValue)}&` : paramString; }, '') + `slots=${bidsInGroup.length}&rand=${Math.random()}`, bidRequest: bidsInGroup }); @@ -471,14 +485,16 @@ export const spec = { size: '*' }); } catch (e) { - utils.logError('Rubicon: getFloor threw an error: ', e); + logError('Rubicon: getFloor threw an error: ', e); } data['rp_hard_floor'] = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined; } // add p_pos only if specified and valid // For SRA we need to explicitly put empty semi colons so AE treats it as empty, instead of copying the latter value - data['p_pos'] = (params.position === 'atf' || params.position === 'btf') ? params.position : ''; + let posMapping = {1: 'atf', 3: 'btf'}; + let pos = posMapping[deepAccess(bidRequest, 'mediaTypes.banner.pos')] || ''; + data['p_pos'] = (params.position === 'atf' || params.position === 'btf') ? params.position : pos; // pass publisher provided userId if configured const configUserId = config.getConfig('user.id'); @@ -501,8 +517,6 @@ export const spec = { } } else if (eid.source === 'liveramp.com') { data['x_liverampidl'] = eid.uids[0].id; - } else if (eid.source === 'sharedid.org') { - data['eid_sharedid.org'] = `${eid.uids[0].id}^${eid.uids[0].atype}^${(eid.uids[0].ext && eid.uids[0].ext.third) || ''}`; } else if (eid.source === 'id5-sync.com') { data['eid_id5-sync.com'] = `${eid.uids[0].id}^${eid.uids[0].atype}^${(eid.uids[0].ext && eid.uids[0].ext.linkType) || ''}`; } else { @@ -518,7 +532,7 @@ export const spec = { } } } catch (e) { - utils.logWarn('Rubicon: error reading eid:', eid, e); + logWarn('Rubicon: error reading eid:', eid, e); } }); } @@ -591,9 +605,9 @@ export const spec = { // video response from PBS Java openRTB if (responseObj.seatbid) { - const responseErrors = utils.deepAccess(responseObj, 'ext.errors.rubicon'); + const responseErrors = deepAccess(responseObj, 'ext.errors.rubicon'); if (Array.isArray(responseErrors) && responseErrors.length > 0) { - utils.logWarn('Rubicon: Error in video response'); + logWarn('Rubicon: Error in video response'); } const bids = []; responseObj.seatbid.forEach(seatbid => { @@ -606,8 +620,8 @@ export const spec = { bidderCode: seatbid.seat, ttl: 300, netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true - width: bid.w || utils.deepAccess(bidRequest, 'mediaTypes.video.w') || utils.deepAccess(bidRequest, 'params.video.playerWidth'), - height: bid.h || utils.deepAccess(bidRequest, 'mediaTypes.video.h') || utils.deepAccess(bidRequest, 'params.video.playerHeight'), + width: bid.w || deepAccess(bidRequest, 'mediaTypes.video.w') || deepAccess(bidRequest, 'params.video.playerWidth'), + height: bid.h || deepAccess(bidRequest, 'mediaTypes.video.h') || deepAccess(bidRequest, 'params.video.playerHeight'), }; if (bid.id) { @@ -619,22 +633,22 @@ export const spec = { } if (bid.adomain) { - utils.deepSetValue(bidObject, 'meta.advertiserDomains', Array.isArray(bid.adomain) ? bid.adomain : [bid.adomain]); + deepSetValue(bidObject, 'meta.advertiserDomains', Array.isArray(bid.adomain) ? bid.adomain : [bid.adomain]); } - if (utils.deepAccess(bid, 'ext.bidder.rp.advid')) { - utils.deepSetValue(bidObject, 'meta.advertiserId', bid.ext.bidder.rp.advid); + if (deepAccess(bid, 'ext.bidder.rp.advid')) { + deepSetValue(bidObject, 'meta.advertiserId', bid.ext.bidder.rp.advid); } - let serverResponseTimeMs = utils.deepAccess(responseObj, 'ext.responsetimemillis.rubicon'); + let serverResponseTimeMs = deepAccess(responseObj, 'ext.responsetimemillis.rubicon'); if (bidRequest && serverResponseTimeMs) { bidRequest.serverResponseTimeMs = serverResponseTimeMs; } - if (utils.deepAccess(bid, 'ext.prebid.type') === VIDEO) { + if (deepAccess(bid, 'ext.prebid.type') === VIDEO) { bidObject.mediaType = VIDEO; - utils.deepSetValue(bidObject, 'meta.mediaType', VIDEO); - const extPrebidTargeting = utils.deepAccess(bid, 'ext.prebid.targeting'); + deepSetValue(bidObject, 'meta.mediaType', VIDEO); + const extPrebidTargeting = deepAccess(bid, 'ext.prebid.targeting'); // If ext.prebid.targeting exists, add it as a property value named 'adserverTargeting' if (extPrebidTargeting && typeof extPrebidTargeting === 'object') { @@ -656,12 +670,12 @@ export const spec = { if (bid.nurl) { bidObject.vastUrl = bid.nurl; } if (!bidObject.vastUrl && bid.nurl) { bidObject.vastUrl = bid.nurl; } - const videoContext = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); if (videoContext.toLowerCase() === 'outstream') { bidObject.renderer = outstreamRenderer(bidObject); } } else { - utils.logWarn('Rubicon: video response received non-video media type'); + logWarn('Rubicon: video response received non-video media type'); } bids.push(bidObject); @@ -740,7 +754,7 @@ export const spec = { bids.push(bid); } else { - utils.logError(`Rubicon: bidRequest undefined at index position:${i}`, bidRequest, responseObj); + logError(`Rubicon: bidRequest undefined at index position:${i}`, bidRequest, responseObj); } return bids; @@ -751,21 +765,23 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { if (!hasSynced && syncOptions.iframeEnabled) { // data is only assigned if params are available to pass to syncEndpoint - let params = ''; + let params = {}; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - // add 'gdpr' only if 'gdprApplies' is defined + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { - params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - params += `?gdpr_consent=${gdprConsent.consentString}`; + params['gdpr'] = Number(gdprConsent.gdprApplies); + } + if (typeof gdprConsent.consentString === 'string') { + params['gdpr_consent'] = gdprConsent.consentString; } } if (uspConsent) { - params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; + params['us_privacy'] = encodeURIComponent(uspConsent); } + params = Object.keys(params).length ? `?${formatQS(params)}` : ''; + hasSynced = true; return { type: 'iframe', @@ -780,7 +796,7 @@ export const spec = { * @return {Object} params bid params */ transformBidParams: function(params, isOpenRtb) { - return utils.convertTypes({ + return convertTypes({ 'accountId': 'number', 'siteId': 'number', 'zoneId': 'number' @@ -848,7 +864,7 @@ function renderBid(bid) { height: bid.height, vastUrl: bid.vastUrl, placement: { - attachTo: `#${bid.adUnitCode}`, + attachTo: adUnitElement, align: config.align || 'center', position: config.position || 'append' }, @@ -871,7 +887,7 @@ function outstreamRenderer(rtbBid) { try { renderer.setRender(renderBid); } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); + logWarn('Prebid Error calling setRender on renderer', err); } return renderer; @@ -886,7 +902,7 @@ function parseSizes(bid, mediaType) { params.video.playerWidth, params.video.playerHeight ]; - } else if (Array.isArray(utils.deepAccess(bid, 'mediaTypes.video.playerSize')) && bid.mediaTypes.video.playerSize.length === 1) { + } else if (Array.isArray(deepAccess(bid, 'mediaTypes.video.playerSize')) && bid.mediaTypes.video.playerSize.length === 1) { size = bid.mediaTypes.video.playerSize[0]; } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0 && Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1) { size = bid.sizes[0]; @@ -898,12 +914,12 @@ function parseSizes(bid, mediaType) { let sizes = []; if (Array.isArray(params.sizes)) { sizes = params.sizes; - } else if (typeof utils.deepAccess(bid, 'mediaTypes.banner.sizes') !== 'undefined') { + } else if (typeof deepAccess(bid, 'mediaTypes.banner.sizes') !== 'undefined') { sizes = mapSizes(bid.mediaTypes.banner.sizes); } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { sizes = mapSizes(bid.sizes) } else { - utils.logWarn('Rubicon: no sizes are setup or found'); + logWarn('Rubicon: no sizes are setup or found'); } return masSizeOrdering(sizes); @@ -932,7 +948,11 @@ function appendSiteAppDevice(data, bidRequest, bidderRequest) { if (bidRequest.params.video.language) { ['site', 'device'].forEach(function(param) { if (data[param]) { - data[param].content = Object.assign({language: bidRequest.params.video.language}, data[param].content) + if (param === 'site') { + data[param].content = Object.assign({language: bidRequest.params.video.language}, data[param].content) + } else { + data[param] = Object.assign({language: bidRequest.params.video.language}, data[param]) + } } }); } @@ -970,16 +990,18 @@ function applyFPD(bidRequest, mediaType, data) { site: {ext: {data: {...bidRequest.params.inventory}}} }; - if (bidRequest.params.keywords) BID_FPD.site.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; + if (bidRequest.params.keywords) BID_FPD.site.keywords = (isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; + + let fpd = mergeDeep({}, config.getConfig('ortb2') || {}, BID_FPD); + let impData = deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; - let fpd = utils.mergeDeep({}, config.getConfig('ortb2') || {}, BID_FPD); - let impData = utils.deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; - const SEGTAX = {user: [3], site: [1, 2]}; + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); + const SEGTAX = {user: [4], site: [1, 2, 5, 6]}; const MAP = {user: 'tg_v.', site: 'tg_i.', adserver: 'tg_i.dfp_ad_unit_code', pbadslot: 'tg_i.pbadslot', keywords: 'kw'}; const validate = function(prop, key, parentName) { if (key === 'data' && Array.isArray(prop)) { - return prop.filter(name => name.segment && utils.deepAccess(name, 'ext.segtax') && SEGTAX[parentName] && - SEGTAX[parentName].indexOf(utils.deepAccess(name, 'ext.segtax')) !== -1).map(value => { + return prop.filter(name => name.segment && deepAccess(name, 'ext.segtax') && SEGTAX[parentName] && + SEGTAX[parentName].indexOf(deepAccess(name, 'ext.segtax')) !== -1).map(value => { let segments = value.segment.filter(obj => obj.id).reduce((result, obj) => { result.push(obj.id); return result; @@ -987,12 +1009,12 @@ function applyFPD(bidRequest, mediaType, data) { if (segments.length > 0) return segments.toString(); }).toString(); } else if (typeof prop === 'object' && !Array.isArray(prop)) { - utils.logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints'); + logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints'); } else if (typeof prop !== 'undefined') { return (Array.isArray(prop)) ? prop.filter(value => { if (typeof value !== 'object' && typeof value !== 'undefined') return value.toString(); - utils.logWarn('Rubicon: Filtered value: ', value, 'for key', key, ': Expected value to be string, integer, or an array of strings/ints'); + logWarn('Rubicon: Filtered value: ', value, 'for key', key, ': Expected value to be string, integer, or an array of strings/ints'); }).toString() : prop.toString(); } }; @@ -1002,16 +1024,6 @@ function applyFPD(bidRequest, mediaType, data) { data[loc] = (data[loc]) ? data[loc].concat(',', val) : val; } - Object.keys(impData).forEach((key) => { - if (key === 'adserver') { - ['name', 'adslot'].forEach(prop => { - if (impData[key][prop]) impData[key][prop] = impData[key][prop].toString().replace(/^\/+/, ''); - }); - } else if (key === 'pbadslot') { - impData[key] = impData[key].toString().replace(/^\/+/, ''); - } - }); - if (mediaType === BANNER) { ['site', 'user'].forEach(name => { Object.keys(fpd[name]).forEach((key) => { @@ -1027,14 +1039,32 @@ function applyFPD(bidRequest, mediaType, data) { }); }); Object.keys(impData).forEach((key) => { - (key === 'adserver') ? addBannerData(impData[key].adslot, name, key) : addBannerData(impData[key], 'site', key); + if (key !== 'adserver') { + addBannerData(impData[key], 'site', key); + } else if (impData[key].name === 'gam') { + addBannerData(impData[key].adslot, name, key) + } }); + + // add in gpid + if (gpid) { + data['p_gpid'] = gpid; + } + + // only send one of pbadslot or dfp adunit code (prefer pbadslot) + if (data['tg_i.pbadslot']) { + delete data['tg_i.dfp_ad_unit_code']; + } } else { if (Object.keys(impData).length) { - utils.mergeDeep(data.imp[0].ext, {data: impData}); + mergeDeep(data.imp[0].ext, {data: impData}); + } + // add in gpid + if (gpid) { + data.imp[0].ext.gpid = gpid; } - utils.mergeDeep(data, fpd); + mergeDeep(data, fpd); } } @@ -1043,7 +1073,7 @@ function applyFPD(bidRequest, mediaType, data) { * @returns {*} */ function mapSizes(sizes) { - return utils.parseSizesInput(sizes) + return parseSizesInput(sizes) // map sizes while excluding non-matches .reduce((result, size) => { let mappedSize = parseInt(sizeMap[size], 10); @@ -1061,10 +1091,10 @@ function mapSizes(sizes) { * @returns {boolean} */ export function hasVideoMediaType(bidRequest) { - if (typeof utils.deepAccess(bidRequest, 'params.video') !== 'object') { + if (typeof deepAccess(bidRequest, 'params.video') !== 'object') { return false; } - return (typeof utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`) !== 'undefined'); + return (typeof deepAccess(bidRequest, `mediaTypes.${VIDEO}`) !== 'undefined'); } /** @@ -1078,9 +1108,9 @@ function bidType(bid, log = false) { if (hasVideoMediaType(bid)) { // Removed legacy mediaType support. new way using mediaTypes.video object is now required // We require either context as instream or outstream - if (['outstream', 'instream'].indexOf(utils.deepAccess(bid, `mediaTypes.${VIDEO}.context`)) === -1) { + if (['outstream', 'instream'].indexOf(deepAccess(bid, `mediaTypes.${VIDEO}.context`)) === -1) { if (log) { - utils.logError('Rubicon: mediaTypes.video.context must be outstream or instream'); + logError('Rubicon: mediaTypes.video.context must be outstream or instream'); } return; } @@ -1088,13 +1118,13 @@ function bidType(bid, log = false) { // we require playerWidth and playerHeight to come from one of params.playerWidth/playerHeight or mediaTypes.video.playerSize or adUnit.sizes if (parseSizes(bid, 'video').length < 2) { if (log) { - utils.logError('Rubicon: could not determine the playerSize of the video'); + logError('Rubicon: could not determine the playerSize of the video'); } return; } if (log) { - utils.logMessage('Rubicon: making video request for adUnit', bid.adUnitCode); + logMessage('Rubicon: making video request for adUnit', bid.adUnitCode); } return 'video'; } else { @@ -1102,14 +1132,14 @@ function bidType(bid, log = false) { // if we cannot determine them, we reject it! if (parseSizes(bid, 'banner').length === 0) { if (log) { - utils.logError('Rubicon: could not determine the sizes for banner request'); + logError('Rubicon: could not determine the sizes for banner request'); } return; } // everything looks good for banner so lets do it if (log) { - utils.logMessage('Rubicon: making banner request for adUnit', bid.adUnitCode); + logMessage('Rubicon: making banner request for adUnit', bid.adUnitCode); } return 'banner'; } @@ -1141,13 +1171,13 @@ export function masSizeOrdering(sizes) { export function determineRubiconVideoSizeId(bid) { // If we have size_id in the bid then use it - let rubiconSizeId = parseInt(utils.deepAccess(bid, 'params.video.size_id')); + let rubiconSizeId = parseInt(deepAccess(bid, 'params.video.size_id')); if (!isNaN(rubiconSizeId)) { return rubiconSizeId; } // otherwise 203 for outstream and 201 for instream // When this function is used we know it has to be one of outstream or instream - return utils.deepAccess(bid, `mediaTypes.${VIDEO}.context`) === 'outstream' ? 203 : 201; + return deepAccess(bid, `mediaTypes.${VIDEO}.context`) === 'outstream' ? 203 : 201; } /** @@ -1190,9 +1220,9 @@ export function hasValidVideoParams(bid) { } // loop through each param and verify it has the correct Object.keys(requiredParams).forEach(function(param) { - if (Object.prototype.toString.call(utils.deepAccess(bid, 'mediaTypes.video.' + param)) !== requiredParams[param]) { + if (Object.prototype.toString.call(deepAccess(bid, 'mediaTypes.video.' + param)) !== requiredParams[param]) { isValid = false; - utils.logError('Rubicon: mediaTypes.video.' + param + ' is required and must be of type: ' + requiredParams[param]); + logError('Rubicon: mediaTypes.video.' + param + ' is required and must be of type: ' + requiredParams[param]); } }) return isValid; @@ -1211,7 +1241,7 @@ export function hasValidSupplyChainParams(schain) { if (!status) return status; return requiredFields.every(field => node.hasOwnProperty(field)); }, true); - if (!isValid) utils.logError('Rubicon: required schain params missing'); + if (!isValid) logError('Rubicon: required schain params missing'); return isValid; } diff --git a/modules/saambaaBidAdapter.js b/modules/saambaaBidAdapter.js old mode 100755 new mode 100644 index 0e53d2a300d..2810853532d --- a/modules/saambaaBidAdapter.js +++ b/modules/saambaaBidAdapter.js @@ -1,401 +1,420 @@ -import * as utils 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'; - -const ADAPTER_VERSION = '1.0'; -const BIDDER_CODE = 'saambaa'; - -export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; -export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; -export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; -export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip']; -export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; - -let pubid = ''; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], - - isBidRequestValid(bidRequest) { - if (typeof bidRequest != 'undefined') { - if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; } - if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; } - return true; - } else { return false; } - }, - - buildRequests(bids, bidderRequest) { - let requests = []; - let videoBids = bids.filter(bid => isVideoBidValid(bid)); - let bannerBids = bids.filter(bid => isBannerBidValid(bid)); - videoBids.forEach(bid => { - pubid = getVideoBidParam(bid, 'pubid'); - requests.push({ - method: 'POST', - url: VIDEO_ENDPOINT + pubid, - data: createVideoRequestData(bid, bidderRequest), - bidRequest: bid - }); - }); - - bannerBids.forEach(bid => { - pubid = getBannerBidParam(bid, 'pubid'); - - requests.push({ - method: 'POST', - url: BANNER_ENDPOINT + pubid, - data: createBannerRequestData(bid, bidderRequest), - bidRequest: bid - }); - }); - return requests; - }, - - interpretResponse(serverResponse, {bidRequest}) { - let response = serverResponse.body; - if (response !== null && utils.isEmpty(response) == false) { - if (isVideoBid(bidRequest)) { - let bidResponse = { - requestId: response.id, - bidderCode: BIDDER_CODE, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].crid, - currency: response.cur, - meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, - mediaType: VIDEO, - netRevenue: true - } - - if (response.seatbid[0].bid[0].adm) { - bidResponse.vastXml = response.seatbid[0].bid[0].adm; - bidResponse.adResponse = { - content: response.seatbid[0].bid[0].adm - }; - } else { - bidResponse.vastUrl = response.seatbid[0].bid[0].nurl; - } - - return bidResponse; - } else { - return { - requestId: response.id, - bidderCode: BIDDER_CODE, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ad: response.seatbid[0].bid[0].adm, - ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].crid, - currency: response.cur, - meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, - mediaType: BANNER, - netRevenue: true - } - } - } - } -}; - -function isBannerBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); -} - -function isVideoBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.video'); -} - -function isVideoBidValid(bid) { - return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); -} - -function isBannerBidValid(bid) { - return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); -} - -function getVideoBidParam(bid, key) { - return utils.deepAccess(bid, 'params.video.' + key) || utils.deepAccess(bid, 'params.' + key); -} - -function getBannerBidParam(bid, key) { - return utils.deepAccess(bid, 'params.banner.' + key) || utils.deepAccess(bid, 'params.' + key); -} - -function isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); -} - -function isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); -} - -function getDoNotTrack() { - return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; -} - -function findAndFillParam(o, key, value) { - try { - if (typeof value === 'function') { - o[key] = value(); - } else { - o[key] = value; - } - } catch (ex) {} -} - -function getOsVersion() { - let clientStrings = [ - { s: 'Android', r: /Android/ }, - { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, - { s: 'Mac OS X', r: /Mac OS X/ }, - { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, - { s: 'Linux', r: /(Linux|X11)/ }, - { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, - { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, - { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, - { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, - { s: 'Windows Vista', r: /Windows NT 6.0/ }, - { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, - { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, - { s: 'UNIX', r: /UNIX/ }, - { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } - ]; - let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); - return cs ? cs.s : 'unknown'; -} - -function getFirstSize(sizes) { - return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; -} - -function parseSizes(sizes) { - return utils.parseSizesInput(sizes).map(size => { - let [ width, height ] = size.split('x'); - return { - w: parseInt(width, 10) || undefined, - h: parseInt(height, 10) || undefined - }; - }); -} - -function getVideoSizes(bid) { - return parseSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); -} - -function getBannerSizes(bid) { - return parseSizes(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); -} - -function getTopWindowReferrer() { - try { - return window.top.document.referrer; - } catch (e) { - return ''; - } -} - -function getVideoTargetingParams(bid) { - return Object.keys(Object(bid.params.video)) - .filter(param => includes(VIDEO_TARGETING, param)) - .reduce((obj, param) => { - obj[ param ] = bid.params.video[ param ]; - return obj; - }, {}); -} - -function createVideoRequestData(bid, bidderRequest) { - let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(); - - // if size is explicitly given via adapter params - let paramSize = getVideoBidParam(bid, 'size'); - let sizes = []; - - if (typeof paramSize !== 'undefined' && paramSize != '') { - sizes = parseSizes(paramSize); - } else { - sizes = getVideoSizes(bid); - } - const firstSize = getFirstSize(sizes); - - let video = getVideoTargetingParams(bid); - const o = { - 'device': { - 'langauge': (global.navigator.language).split('-')[0], - 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), - 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, - 'js': 1, - 'os': getOsVersion() - }, - 'at': 2, - 'site': {}, - 'tmax': 3000, - 'cur': ['USD'], - 'id': bid.bidId, - 'imp': [], - 'regs': { - 'ext': { - } - }, - 'user': { - 'ext': { - } - } - }; - - o.site['page'] = topLocation.href; - o.site['domain'] = topLocation.hostname; - o.site['search'] = topLocation.search; - o.site['domain'] = topLocation.hostname; - o.site['ref'] = topReferrer; - o.site['mobile'] = isMobile() ? 1 : 0; - const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; - - o.device['dnt'] = getDoNotTrack() ? 1 : 0; - - findAndFillParam(o.site, 'name', function() { - return global.top.document.title; - }); - - findAndFillParam(o.device, 'h', function() { - return global.screen.height; - }); - findAndFillParam(o.device, 'w', function() { - return global.screen.width; - }); - - let placement = getVideoBidParam(bid, 'placement'); - let floor = getVideoBidParam(bid, 'floor'); - if (floor == null) { floor = 0.5; } - - for (let j = 0; j < sizes.length; j++) { - o.imp.push({ - 'id': '' + j, - 'displaymanager': '' + BIDDER_CODE, - 'displaymanagerver': '' + ADAPTER_VERSION, - 'tagId': placement, - 'bidfloor': floor, - 'bidfloorcur': 'USD', - 'secure': secure, - 'video': Object.assign({ - 'id': utils.generateUUID(), - 'pos': 0, - 'w': firstSize.w, - 'h': firstSize.h, - 'mimes': DEFAULT_MIMES - }, video) - - }); - } - - if (bidderRequest && bidderRequest.gdprConsent) { - let { gdprApplies, consentString } = bidderRequest.gdprConsent; - o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; - o.user.ext = {'consent': consentString}; - } - - return o; -} - -function getTopWindowLocation(bidderRequest) { - let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; - return utils.parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); -} - -function createBannerRequestData(bid, bidderRequest) { - let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(); - - // if size is explicitly given via adapter params - - let paramSize = getBannerBidParam(bid, 'size'); - let sizes = []; - if (typeof paramSize !== 'undefined' && paramSize != '') { - sizes = parseSizes(paramSize); - } else { - sizes = getBannerSizes(bid); - } - - const o = { - 'device': { - 'langauge': (global.navigator.language).split('-')[0], - 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), - 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, - 'js': 1 - }, - 'at': 2, - 'site': {}, - 'tmax': 3000, - 'cur': ['USD'], - 'id': bid.bidId, - 'imp': [], - 'regs': { - 'ext': { - } - }, - 'user': { - 'ext': { - } - } - }; - - o.site['page'] = topLocation.href; - o.site['domain'] = topLocation.hostname; - o.site['search'] = topLocation.search; - o.site['domain'] = topLocation.hostname; - o.site['ref'] = topReferrer; - o.site['mobile'] = isMobile() ? 1 : 0; - const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; - - o.device['dnt'] = getDoNotTrack() ? 1 : 0; - - findAndFillParam(o.site, 'name', function() { - return global.top.document.title; - }); - - findAndFillParam(o.device, 'h', function() { - return global.screen.height; - }); - findAndFillParam(o.device, 'w', function() { - return global.screen.width; - }); - - let placement = getBannerBidParam(bid, 'placement'); - for (let j = 0; j < sizes.length; j++) { - let size = sizes[j]; - - let floor = getBannerBidParam(bid, 'floor'); - if (floor == null) { floor = 0.1; } - - o.imp.push({ - 'id': '' + j, - 'displaymanager': '' + BIDDER_CODE, - 'displaymanagerver': '' + ADAPTER_VERSION, - 'tagId': placement, - 'bidfloor': floor, - 'bidfloorcur': 'USD', - 'secure': secure, - 'banner': { - 'id': utils.generateUUID(), - 'pos': 0, - 'w': size['w'], - 'h': size['h'] - } - }); - } - - if (bidderRequest && bidderRequest.gdprConsent) { - let { gdprApplies, consentString } = bidderRequest.gdprConsent; - o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; - o.user.ext = {'consent': consentString}; - } - - return o; -} -registerBidder(spec); +import { deepAccess, isFn, generateUUID, parseUrl, isEmpty, parseSizesInput } 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'; + +const ADAPTER_VERSION = '1.0'; +const BIDDER_CODE = 'saambaa'; + +export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip', 'playerSize', 'context']; +export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; + +let pubid = ''; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid(bidRequest) { + if (typeof bidRequest != 'undefined') { + if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; } + if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; } + return true; + } else { return false; } + }, + + buildRequests(bids, bidderRequest) { + let requests = []; + let videoBids = bids.filter(bid => isVideoBidValid(bid)); + let bannerBids = bids.filter(bid => isBannerBidValid(bid)); + videoBids.forEach(bid => { + pubid = getVideoBidParam(bid, 'pubid'); + requests.push({ + method: 'POST', + url: VIDEO_ENDPOINT + pubid, + data: createVideoRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + + bannerBids.forEach(bid => { + pubid = getBannerBidParam(bid, 'pubid'); + + requests.push({ + method: 'POST', + url: BANNER_ENDPOINT + pubid, + data: createBannerRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + return requests; + }, + + interpretResponse(serverResponse, {bidRequest}) { + let response = serverResponse.body; + if (response !== null && isEmpty(response) == false) { + if (isVideoBid(bidRequest)) { + let bidResponse = { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, + mediaType: VIDEO, + netRevenue: true + } + + if (response.seatbid[0].bid[0].adm) { + bidResponse.vastXml = response.seatbid[0].bid[0].adm; + bidResponse.adResponse = { + content: response.seatbid[0].bid[0].adm + }; + } else { + bidResponse.vastUrl = response.seatbid[0].bid[0].nurl; + } + + return bidResponse; + } else { + return { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ad: response.seatbid[0].bid[0].adm, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, + mediaType: BANNER, + netRevenue: true + } + } + } + } +}; + +function isBannerBid(bid) { + return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +function isVideoBid(bid) { + return deepAccess(bid, 'mediaTypes.video'); +} + +function getBannerBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {}; + return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); +} + +function getVideoBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'video', size: '*' }) : {}; + return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); +} + +function isVideoBidValid(bid) { + return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); +} + +function isBannerBidValid(bid) { + return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); +} + +function getVideoBidParam(bid, key) { + return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); +} + +function getBannerBidParam(bid, key) { + return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key); +} + +function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function getDoNotTrack() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; +} + +function findAndFillParam(o, key, value) { + try { + if (typeof value === 'function') { + o[key] = value(); + } else { + o[key] = value; + } + } catch (ex) {} +} + +function getOsVersion() { + let clientStrings = [ + { s: 'Android', r: /Android/ }, + { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, + { s: 'Mac OS X', r: /Mac OS X/ }, + { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, + { s: 'Linux', r: /(Linux|X11)/ }, + { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, + { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, + { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, + { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, + { s: 'Windows Vista', r: /Windows NT 6.0/ }, + { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, + { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, + { s: 'UNIX', r: /UNIX/ }, + { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } + ]; + let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); + return cs ? cs.s : 'unknown'; +} + +function getFirstSize(sizes) { + return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; +} + +function parseSizes(sizes) { + return parseSizesInput(sizes).map(size => { + let [ width, height ] = size.split('x'); + return { + w: parseInt(width, 10) || undefined, + h: parseInt(height, 10) || undefined + }; + }); +} + +function getVideoSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +function getBannerSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +function getTopWindowReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return ''; + } +} + +function getVideoTargetingParams(bid) { + const result = {}; + const excludeProps = ['playerSize', 'context', 'w', 'h']; + Object.keys(Object(bid.mediaTypes.video)) + .filter(key => !includes(excludeProps, 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 ]; + }); + return result; +} + +function createVideoRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + // if size is explicitly given via adapter params + let paramSize = getVideoBidParam(bid, 'size'); + let sizes = []; + let coppa = config.getConfig('coppa'); + + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getVideoSizes(bid); + } + const firstSize = getFirstSize(sizes); + let floor = (getVideoBidFloor(bid) == null || typeof getVideoBidFloor(bid) == 'undefined') ? 0.5 : getVideoBidFloor(bid); + let video = getVideoTargetingParams(bid); + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1, + 'os': getOsVersion() + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getVideoBidParam(bid, 'placement'); + + for (let j = 0; j < sizes.length; j++) { + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': floor, + 'bidfloorcur': 'USD', + 'secure': secure, + 'video': Object.assign({ + 'id': generateUUID(), + 'pos': 0, + 'w': firstSize.w, + 'h': firstSize.h, + 'mimes': DEFAULT_MIMES + }, video) + + }); + } + if (coppa) { + o.regs.ext = {'coppa': 1}; + } + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} + +function getTopWindowLocation(bidderRequest) { + let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + return parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); +} + +function createBannerRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + // if size is explicitly given via adapter params + + let paramSize = getBannerBidParam(bid, 'size'); + let sizes = []; + let coppa = config.getConfig('coppa'); + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getBannerSizes(bid); + } + + let floor = (getBannerBidFloor(bid) == null || typeof getBannerBidFloor(bid) == 'undefined') ? 0.1 : getBannerBidFloor(bid); + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1 + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getBannerBidParam(bid, 'placement'); + for (let j = 0; j < sizes.length; j++) { + let size = sizes[j]; + + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': floor, + 'bidfloorcur': 'USD', + 'secure': secure, + 'banner': { + 'id': generateUUID(), + 'pos': 0, + 'w': size['w'], + 'h': size['h'] + } + }); + } + if (coppa) { + o.regs.ext = {'coppa': 1}; + } + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} +registerBidder(spec); diff --git a/modules/saambaaBidAdapter.md b/modules/saambaaBidAdapter.md index 2d391da7628..d58e3f0abfa 100755 --- a/modules/saambaaBidAdapter.md +++ b/modules/saambaaBidAdapter.md @@ -1,69 +1,66 @@ -# Overview - -``` -Module Name: Saambaa Bidder Adapter -Module Type: Bidder Adapter -Maintainer: matt.voigt@saambaa.com -``` - -# Description - -Connects to Saambaa exchange for bids. - -Saambaa bid adapter supports Banner and Video ads currently. - -For more informatio - -# Sample Display Ad Unit: For Publishers -```javascript - -var displayAdUnit = [ -{ - code: 'display', - mediaTypes: { - banner: { - sizes: [[300, 250],[320, 50]] - } - } - bids: [{ - bidder: 'saambaa', - params: { - pubid: '121ab139faf7ac67428a23f1d0a9a71b', - placement: 1234, - size: '320x50' - } - }] -}]; -``` - -# Sample Video Ad Unit: For Publishers -```javascript - -var videoAdUnit = { - code: 'video', - sizes: [320,480], - mediaTypes: { - video: { - playerSize : [[320, 480]], - context: 'instream' - } - }, - bids: [ - { - bidder: 'saambaa', - params: { - pubid: '121ab139faf7ac67428a23f1d0a9a71b', - placement: 1234, - size: "320x480", - video: { - id: 123, - skip: 1, - mimes : ['video/mp4', 'application/javascript'], - playbackmethod : [2,6], - maxduration: 30 - } - } - } - ] - }; +# Overview + +``` +Module Name: Saambaa Bidder Adapter +Module Type: Bidder Adapter +Maintainer: matt.voigt@saambaa.com +``` + +# Description + +Connects to Saambaa exchange for bids. + +Saambaa bid adapter supports Banner and Video ads currently. + +For more informatio + +# Sample Display Ad Unit: For Publishers +```javascript + +var displayAdUnit = [ +{ + code: 'display', + mediaTypes: { + banner: { + sizes: [[300, 250],[320, 50]] + } + } + bids: [{ + bidder: 'saambaa', + params: { + pubid: '121ab139faf7ac67428a23f1d0a9a71b', + placement: 1234, + size: '320x50' + } + }] +}]; +``` + +# Sample Video Ad Unit: For Publishers +```javascript + +var videoAdUnit = { + code: 'video', + sizes: [320,480], + mediaTypes: { + video: { + playerSize : [[320, 480]], + context: 'instream', + skip: 1, + mimes : ['video/mp4', 'application/javascript'], + playbackmethod : [2,6], + maxduration: 30 + } + }, + bids: [ + { + bidder: 'saambaa', + params: { + pubid: '121ab139faf7ac67428a23f1d0a9a71b', + placement: 1234, + size: "320x480" + } + } + ] + }; ``` \ No newline at end of file diff --git a/modules/scaleableAnalyticsAdapter.js b/modules/scaleableAnalyticsAdapter.js index 955a08c065a..d7379462e0d 100644 --- a/modules/scaleableAnalyticsAdapter.js +++ b/modules/scaleableAnalyticsAdapter.js @@ -4,7 +4,7 @@ import { ajax } from '../src/ajax.js'; import CONSTANTS from '../src/constants.json'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import * as utils from '../src/utils.js'; +import { logMessage } from '../src/utils.js'; // Object.entries polyfill const entries = Object.entries || function(obj) { @@ -62,7 +62,7 @@ scaleableAnalytics.enableAnalytics = config => { scaleableAnalytics.originEnableAnalytics(config); scaleableAnalytics.enableAnalytics = function _enable() { - return utils.logMessage(`Analytics adapter for "${global}" already enabled, unnecessary call to \`enableAnalytics\`.`); + return logMessage(`Analytics adapter for "${global}" already enabled, unnecessary call to \`enableAnalytics\`.`); }; } diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index d85ae856317..b7aec0f8881 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -3,7 +3,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { NATIVE } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; +import { _map, deepSetValue, isEmpty, deepAccess } from '../src/utils.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'seedingAlliance'; @@ -65,7 +65,7 @@ export const spec = { let url = bidderRequest.refererInfo.referer; const imp = validBidRequests.map((bid, id) => { - const assets = utils._map(bid.nativeParams, (bidParams, key) => { + const assets = _map(bid.nativeParams, (bidParams, key) => { const props = NATIVE_PARAMS[key]; const asset = { @@ -130,8 +130,8 @@ export const spec = { }; if (bidderRequest && bidderRequest.gdprConsent) { - utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - utils.deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); } return { @@ -146,16 +146,16 @@ export const spec = { }, interpretResponse: function(serverResponse, { bids }) { - if (utils.isEmpty(serverResponse.body)) { + if (isEmpty(serverResponse.body)) { return []; } const { seatbid, cur } = serverResponse.body; - const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { + const bidResponses = (typeof seatbid != 'undefined') ? flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { result[bid.impid - 1] = bid; return result; - }, []); + }, []) : []; return bids .map((bid, id) => { @@ -167,11 +167,14 @@ export const spec = { cpm: bidResponse.price, creativeId: bidResponse.crid, ttl: 1000, - netRevenue: bid.netRevenue === 'net', + netRevenue: (!bid.netRevenue || bid.netRevenue === 'net'), currency: cur, mediaType: NATIVE, bidderCode: BIDDER_CODE, - native: parseNative(bidResponse) + native: parseNative(bidResponse), + meta: { + advertiserDomains: bidResponse.adomain && bidResponse.adomain.length > 0 ? bidResponse.adomain : [] + } }; } }) @@ -184,13 +187,16 @@ registerBidder(spec); function parseNative(bid) { const {assets, link, imptrackers} = bid.adm.native; - link.clicktrackers.forEach(function (clicktracker, index) { - link.clicktrackers[index] = clicktracker.replace(/\$\{AUCTION_PRICE\}/, bid.price); - }); - - imptrackers.forEach(function (imptracker, index) { - imptrackers[index] = imptracker.replace(/\$\{AUCTION_PRICE\}/, bid.price); - }); + if (link.clicktrackers) { + link.clicktrackers.forEach(function (clicktracker, index) { + link.clicktrackers[index] = clicktracker.replace(/\$\{AUCTION_PRICE\}/, bid.price); + }); + } + if (imptrackers) { + imptrackers.forEach(function (imptracker, index) { + imptrackers[index] = imptracker.replace(/\$\{AUCTION_PRICE\}/, bid.price); + }); + } const result = { url: link.url, @@ -213,7 +219,7 @@ function parseNative(bid) { function setOnAny(collection, key) { for (let i = 0, result; i < collection.length; i++) { - result = utils.deepAccess(collection[i], key); + result = deepAccess(collection[i], key); if (result) { return result; } diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index c25d833a71c..bae27d41028 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js' +import { isArray, _map, triggerPixel } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import { VIDEO, BANNER } from '../src/mediaTypes.js' @@ -13,6 +13,11 @@ const ALLOWED_PLACEMENTS = { banner: true, video: true } + +// Global Vendor List Id +// https://iabeurope.eu/vendor-list-tcf-v2-0/ +const GVLID = 157; + const mediaTypesMap = { [BANNER]: 'display', [VIDEO]: 'video' @@ -46,7 +51,7 @@ function mapMediaType(seedtagMediaType) { } function hasVideoMediaType(bid) { - return !!bid.mediaTypes && !!bid.mediaTypes.video + return (!!bid.mediaTypes && !!bid.mediaTypes.video) || (!!bid.params && !!bid.params.video) } function hasMandatoryParams(params) { @@ -58,51 +63,65 @@ function hasMandatoryParams(params) { ); } -function hasMandatoryVideoParams(mediaTypes) { - const isVideoInStream = - !!mediaTypes.video && mediaTypes.video.context === 'instream'; - const isPlayerSize = - !!utils.deepAccess(mediaTypes, 'video.playerSize') && - utils.isArray(utils.deepAccess(mediaTypes, 'video.playerSize')); - return isVideoInStream && isPlayerSize; -} +function hasMandatoryVideoParams(bid) { + const videoParams = getVideoParams(bid) -function buildBidRequests(validBidRequests) { - return utils._map(validBidRequests, function(validBidRequest) { - const params = validBidRequest.params; - const mediaTypes = utils._map( - Object.keys(validBidRequest.mediaTypes), - function(pbjsType) { - return mediaTypesMap[pbjsType]; - } - ); - - const bidRequest = { - id: validBidRequest.bidId, - transactionId: validBidRequest.transactionId, - sizes: validBidRequest.sizes, - supplyTypes: mediaTypes, - adUnitId: params.adUnitId, - placement: params.placement, - }; + return hasVideoMediaType(bid) && !!videoParams.playerSize && + isArray(videoParams.playerSize) && + videoParams.playerSize.length > 0; +} - if (params.adPosition) { - bidRequest.adPosition = params.adPosition; +function buildBidRequest(validBidRequest) { + const params = validBidRequest.params; + const mediaTypes = _map( + Object.keys(validBidRequest.mediaTypes), + function (pbjsType) { + return mediaTypesMap[pbjsType]; } + ); - if (hasVideoMediaType(validBidRequest)) { - bidRequest.videoParams = params.video || {}; - bidRequest.videoParams.w = - validBidRequest.mediaTypes.video.playerSize[0][0]; - bidRequest.videoParams.h = - validBidRequest.mediaTypes.video.playerSize[0][1]; - } + const bidRequest = { + id: validBidRequest.bidId, + transactionId: validBidRequest.transactionId, + sizes: validBidRequest.sizes, + supplyTypes: mediaTypes, + adUnitId: params.adUnitId, + adUnitCode: validBidRequest.adUnitCode, + placement: params.placement, + requestCount: validBidRequest.bidderRequestsCount || 1 // FIXME : in unit test the parameter bidderRequestsCount is undefined + }; + + if (params.adPosition) { + bidRequest.adPosition = params.adPosition; + } - return bidRequest; + if (hasVideoMediaType(validBidRequest)) { + bidRequest.videoParams = getVideoParams(validBidRequest) + } + + return bidRequest; +} + +/** + * return video param (global or overrided per bidder) + */ +function getVideoParams(validBidRequest) { + const videoParams = validBidRequest.mediaTypes.video || {}; + if (videoParams.playerSize) { + videoParams.w = videoParams.playerSize[0][0]; + videoParams.h = videoParams.playerSize[0][1]; + } + + const bidderVideoParams = (validBidRequest.params && validBidRequest.params.video) || {} + // override video params from seedtag bidder params + Object.keys(bidderVideoParams).forEach(key => { + videoParams[key] = validBidRequest.params.video[key] }) + + return videoParams } -function buildBid(seedtagBid) { +function buildBidResponse(seedtagBid) { const mediaType = mapMediaType(seedtagBid.mediaType); const bid = { requestId: seedtagBid.bidId, @@ -114,7 +133,10 @@ function buildBid(seedtagBid) { netRevenue: true, mediaType: mediaType, ttl: seedtagBid.ttl, - nurl: seedtagBid.nurl + nurl: seedtagBid.nurl, + meta: { + advertiserDomains: seedtagBid && seedtagBid.adomain && seedtagBid.adomain.length > 0 ? seedtagBid.adomain : [] + } }; if (mediaType === VIDEO) { @@ -128,8 +150,8 @@ function buildBid(seedtagBid) { export function getTimeoutUrl (data) { let queryParams = ''; if ( - utils.isArray(data) && data[0] && - utils.isArray(data[0].params) && data[0].params[0] + isArray(data) && data[0] && + isArray(data[0].params) && data[0].params[0] ) { const params = data[0].params[0]; queryParams = @@ -141,9 +163,9 @@ export function getTimeoutUrl (data) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: [SEEDTAG_ALIAS], supportedMediaTypes: [BANNER, VIDEO], - /** * Determines whether or not the given bid request is valid. * @@ -152,7 +174,7 @@ export const spec = { */ isBidRequestValid(bid) { return hasVideoMediaType(bid) - ? hasMandatoryParams(bid.params) && hasMandatoryVideoParams(bid.mediaTypes) + ? hasMandatoryParams(bid.params) && hasMandatoryVideoParams(bid) : hasMandatoryParams(bid.params); }, @@ -170,7 +192,7 @@ export const spec = { timeout: bidderRequest.timeout, version: '$prebid.version$', connectionType: getConnectionType(), - bidRequests: buildBidRequests(validBidRequests) + bidRequests: _map(validBidRequests, buildBidRequest) }; if (payload.cmp) { @@ -195,9 +217,9 @@ export const spec = { */ interpretResponse: function(serverResponse) { const serverBody = serverResponse.body; - if (serverBody && serverBody.bids && utils.isArray(serverBody.bids)) { - return utils._map(serverBody.bids, function(bid) { - return buildBid(bid); + if (serverBody && serverBody.bids && isArray(serverBody.bids)) { + return _map(serverBody.bids, function(bid) { + return buildBidResponse(bid); }); } else { return []; @@ -227,7 +249,7 @@ export const spec = { */ onTimeout(data) { const url = getTimeoutUrl(data); - utils.triggerPixel(url); + triggerPixel(url); }, /** @@ -236,7 +258,7 @@ export const spec = { */ onBidWon: function (bid) { if (bid && bid.nurl) { - utils.triggerPixel(bid.nurl); + triggerPixel(bid.nurl); } } } diff --git a/modules/seedtagBidAdapter.md b/modules/seedtagBidAdapter.md index 627ff8333ad..f4249fb2e89 100644 --- a/modules/seedtagBidAdapter.md +++ b/modules/seedtagBidAdapter.md @@ -46,7 +46,19 @@ var adUnits = [{ mediaTypes: { video: { context: 'instream', // required - playerSize: [600, 300] // required + playerSize: [600, 300], // required + mimes: ['video/mp4'], // recommended + minduration: 5, // optional + maxduration: 60, // optional + boxingallowed: 1, // optional + skip: 1, // optional + startdelay: 1, // optional + linearity: 1, // optional + battr: [1, 2], // optional + maxbitrate: 10, // optional + playbackmethod: [1], // optional + delivery: [1], // optional + placement: 1, // optional } }, bids: [ @@ -57,9 +69,10 @@ var adUnits = [{ adUnitId: '0000', // required placement: 'video', // required adPosition: 0, // optional - // Video object as specified in OpenRTB 2.5 - video: { - mimes: ['video/mp4'], // recommended + video: { // optional + context: 'instream', // optional + playerSize: [600, 300], // optional + mimes: ['video/mp4'], // optional minduration: 5, // optional maxduration: 60, // optional boxingallowed: 1, // optional diff --git a/modules/segmentoBidAdapter.js b/modules/segmentoBidAdapter.js deleted file mode 100644 index a042bdf4942..00000000000 --- a/modules/segmentoBidAdapter.js +++ /dev/null @@ -1,85 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'segmento'; -const URL = 'https://prebid-bidder.rutarget.ru/bid'; -const SYNC_IFRAME_URL = 'https://tag.rutarget.ru/tag?event=otherPage&check=true&response=syncframe&synconly=true'; -const SYNC_IMAGE_URL = 'https://tag.rutarget.ru/tag?event=otherPage&check=true&synconly=true'; -const RUB = 'RUB'; -const TIME_TO_LIVE = 0; - -export const spec = { - code: BIDDER_CODE, - isBidRequestValid: (bid) => { - return Boolean(bid && bid.params && !isNaN(bid.params.placementId)); - }, - buildRequests: (validBidRequests, bidderRequest) => { - const payload = { - places: [], - settings: { - currency: RUB, - referrer: bidderRequest.refererInfo && bidderRequest.refererInfo.referer - } - }; - - for (let i = 0; i < validBidRequests.length; i++) { - const bid = validBidRequests[i]; - - payload.places.push({ - id: bid.bidId, - placementId: bid.params.placementId, - sizes: bid.sizes - }); - } - - return { - method: 'POST', - url: URL, - data: payload - }; - }, - interpretResponse: (serverResponse) => { - const bids = serverResponse.body && serverResponse.body.bids; - if (!bids) { - return []; - } - - const bidResponses = []; - - for (let i = 0; i < bids.length; i++) { - const bid = bids[i]; - - bidResponses.push({ - requestId: bid.id, - cpm: bid.cpm, - width: bid.size.width, - height: bid.size.height, - creativeId: bid.creativeId, - currency: RUB, - netRevenue: true, - ttl: TIME_TO_LIVE, - adUrl: bid.displayUrl - }); - } - - return bidResponses; - }, - getUserSyncs: (syncOptions) => { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: SYNC_IFRAME_URL - }]; - } - - if (syncOptions.pixelEnabled) { - return [{ - type: 'image', - url: SYNC_IMAGE_URL - }]; - } - - return []; - } -}; - -registerBidder(spec); diff --git a/modules/sekindoUMBidAdapter.js b/modules/sekindoUMBidAdapter.js deleted file mode 100644 index bea25173747..00000000000 --- a/modules/sekindoUMBidAdapter.js +++ /dev/null @@ -1,119 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -export const spec = { - code: 'sekindoUM', - 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) { - if (bid.mediaType == 'video' || (typeof bid.mediaTypes == 'object' && typeof bid.mediaTypes.video == 'object')) { - if (typeof bid.params.video != 'object' || typeof bid.params.video.playerWidth == 'undefined' || typeof bid.params.video.playerHeight == 'undefined') { - return false; - } - } - return !!(bid.params.spaceId); - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests, bidderRequest) { - var pubUrl = null; - try { - if (window.top == window) { - pubUrl = window.location.href; - } else { - try { - pubUrl = window.top.location.href; - } catch (e2) { - pubUrl = document.referrer; - } - } - } catch (e1) {} - - return validBidRequests.map(bidRequest => { - var subId = utils.getBidIdParameter('subId', bidRequest.params); - var spaceId = utils.getBidIdParameter('spaceId', bidRequest.params); - var bidfloor = utils.getBidIdParameter('bidfloor', bidRequest.params); - var protocol = (document.location.protocol === 'https:' ? 's' : ''); - var queryString = ''; - - queryString = utils.tryAppendQueryString(queryString, 's', spaceId); - queryString = utils.tryAppendQueryString(queryString, 'subId', subId); - queryString = utils.tryAppendQueryString(queryString, 'pubUrl', pubUrl); - queryString = utils.tryAppendQueryString(queryString, 'hbTId', bidRequest.transactionId); - queryString = utils.tryAppendQueryString(queryString, 'hbBidId', bidRequest.bidId); - queryString = utils.tryAppendQueryString(queryString, 'hbver', '4'); - queryString = utils.tryAppendQueryString(queryString, 'hbcb', '1');/// legasy - queryString = utils.tryAppendQueryString(queryString, 'dcpmflr', bidfloor); - queryString = utils.tryAppendQueryString(queryString, 'protocol', protocol); - queryString = utils.tryAppendQueryString(queryString, 'x', bidRequest.params.width); - queryString = utils.tryAppendQueryString(queryString, 'y', bidRequest.params.height); - if (bidderRequest && bidderRequest.gdprConsent) { - queryString = utils.tryAppendQueryString(queryString, 'gdprConsent', bidderRequest.gdprConsent.consentString); - queryString = utils.tryAppendQueryString(queryString, 'gdpr', (bidderRequest.gdprConsent.gdprApplies) ? '1' : '0'); - } - if (bidRequest.mediaType === 'video' || (typeof bidRequest.mediaTypes == 'object' && typeof bidRequest.mediaTypes.video == 'object')) { - queryString = utils.tryAppendQueryString(queryString, 'x', bidRequest.params.playerWidth); - queryString = utils.tryAppendQueryString(queryString, 'y', bidRequest.params.playerHeight); - if (typeof vid_vastType != 'undefined') { // eslint-disable-line camelcase - queryString = utils.tryAppendQueryString(queryString, 'vid_vastType', bidRequest.params.vid_vastType); - } - if (typeof bidRequest.mediaTypes == 'object' && typeof bidRequest.mediaTypes.video == 'object' && typeof bidRequest.mediaTypes.video.context == 'string') { - queryString = utils.tryAppendQueryString(queryString, 'vid_context', bidRequest.mediaTypes.video.context); - } - } - - var endpointUrl = 'https' + '://hb.sekindo.com/live/liveView.php'; - - return { - method: 'GET', - url: endpointUrl, - data: queryString, - }; - }); - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, bidRequest) { - if (typeof serverResponse !== 'object') { - return []; - } - - let bidResponses = []; - var bidResponse = { - requestId: serverResponse.body.id, - bidderCode: spec.code, - cpm: serverResponse.body.cpm, - width: serverResponse.body.width, - height: serverResponse.body.height, - creativeId: serverResponse.body.creativeId, - currency: serverResponse.body.currency, - netRevenue: serverResponse.body.netRevenue, - ttl: serverResponse.body.ttl - }; - if (bidRequest.mediaType == 'video') { - if (typeof serverResponse.body.vastUrl != 'undefined') { - bidResponse.vastUrl = serverResponse.body.vastUrl; - } else { - bidResponse.vastXml = serverResponse.body.vastXml; - } - } else { - bidResponse.ad = serverResponse.body.ad; - } - - bidResponses.push(bidResponse); - return bidResponses; - } -} -registerBidder(spec); diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index defa8d22639..32a96100d43 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -1,373 +1,203 @@ /** - * This module adds Shared ID support to the User ID module - * The {@link module:modules/userId} module is required. + * This module adds SharedId to the User ID module + * The {@link module:modules/userId} module is required * @module modules/sharedIdSystem * @requires module:modules/userId */ -import * as utils from '../src/utils.js' -import {ajax} from '../src/ajax.js'; +import { parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID } from '../src/utils.js'; import {submodule} from '../src/hook.js'; -import { uspDataHandler, coppaDataHandler } from '../src/adapterManager.js'; +import { coppaDataHandler } from '../src/adapterManager.js'; +import {getStorageManager} from '../src/storageManager.js'; -const MODULE_NAME = 'sharedId'; -const ID_SVC = 'https://id.sharedid.org/id'; -const DEFAULT_24_HOURS = 86400; -const OPT_OUT_VALUE = '00000000000000000000000000'; -// These values should NEVER change. If -// they do, we're no longer making ulids! -const ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; // Crockford's Base32 -const ENCODING_LEN = ENCODING.length; -const TIME_MAX = Math.pow(2, 48) - 1; -const TIME_LEN = 10; -const RANDOM_LEN = 16; -const id = factory(); const GVLID = 887; -/** - * Constructs cookie value - * @param value - * @param needsSync - * @returns {string} - */ -function constructCookieValue(value, needsSync) { - const cookieValue = {}; - cookieValue.id = value; - cookieValue.ts = utils.timestamp(); - if (needsSync) { - cookieValue.ns = true; - } - utils.logInfo('SharedId: cookie Value: ' + JSON.stringify(cookieValue)); - return cookieValue; -} +export const storage = getStorageManager(GVLID, 'pubCommonId'); +const COOKIE = 'cookie'; +const LOCAL_STORAGE = 'html5'; +const OPTOUT_NAME = '_pubcid_optout'; +const PUB_COMMON_ID = 'PublisherCommonId'; /** - * Checks if id needs to be synced - * @param configParams - * @param storedId - * @returns {boolean} + * Read a value either from cookie or local storage + * @param {string} name Name of the item + * @param {string} type storage type override + * @returns {string|null} a string if item exists */ -function isIdSynced(configParams, storedId) { - const needSync = storedId.ns; - if (needSync) { - return true; - } - if (!configParams || typeof configParams.syncTime !== 'number') { - utils.logInfo('SharedId: Sync time is not configured or is not a number'); - } - let syncTime = (!configParams || typeof configParams.syncTime !== 'number') ? DEFAULT_24_HOURS : configParams.syncTime; - if (syncTime > DEFAULT_24_HOURS) { - syncTime = DEFAULT_24_HOURS; - } - const cookieTimestamp = storedId.ts; - if (cookieTimestamp) { - var secondBetweenTwoDate = timeDifferenceInSeconds(utils.timestamp(), cookieTimestamp); - return secondBetweenTwoDate >= syncTime; - } - return false; -} - -/** - * Gets time difference in secounds - * @param date1 - * @param date2 - * @returns {number} - */ -function timeDifferenceInSeconds(date1, date2) { - const diff = (date1 - date2) / 1000; - return Math.abs(Math.round(diff)); -} - -/** - * id generation call back - * @param result - * @param callback - * @returns {{success: success, error: error}} - */ -function idGenerationCallback(callback) { - return { - success: function (responseBody) { - let value = {}; - if (responseBody) { - try { - let responseObj = JSON.parse(responseBody); - utils.logInfo('SharedId: Generated SharedId: ' + responseObj.sharedId); - value = constructCookieValue(responseObj.sharedId, false); - } catch (error) { - utils.logError(error); - } +function readValue(name, type) { + if (type === COOKIE) { + return storage.getCookie(name); + } else if (type === LOCAL_STORAGE) { + if (storage.hasLocalStorage()) { + const expValue = storage.getDataFromLocalStorage(`${name}_exp`); + if (!expValue) { + return storage.getDataFromLocalStorage(name); + } else if ((new Date(expValue)).getTime() - Date.now() > 0) { + return storage.getDataFromLocalStorage(name) } - callback(value); - }, - error: function (statusText, responseBody) { - const value = constructCookieValue(id(), true); - utils.logInfo('SharedId: Ulid Generated SharedId: ' + value.id); - callback(value); } } } -/** - * existing id generation call back - * @param result - * @param callback - * @returns {{success: success, error: error}} - */ -function existingIdCallback(storedId, callback) { - return { - success: function (responseBody) { - utils.logInfo('SharedId: id to be synced: ' + storedId.id); - if (responseBody) { - try { - let responseObj = JSON.parse(responseBody); - storedId = constructCookieValue(responseObj.sharedId, false); - utils.logInfo('SharedId: Older SharedId: ' + storedId.id); - } catch (error) { - utils.logError(error); - } - } - callback(storedId); - }, - error: function () { - utils.logInfo('SharedId: Sync error for id : ' + storedId.id); - callback(storedId); +function getIdCallback(pubcid, pixelCallback) { + return function (callback) { + if (typeof pixelCallback === 'function') { + pixelCallback(); } + callback(pubcid); } } -/** - * Encode the id - * @param value - * @returns {string|*} - */ -function encodeId(value) { - const result = {}; - const sharedId = (value && typeof value['id'] === 'string') ? value['id'] : undefined; - if (sharedId == OPT_OUT_VALUE) { - return undefined; - } - if (sharedId) { - const bidIds = { - id: sharedId, - } - const ns = (value && typeof value['ns'] === 'boolean') ? value['ns'] : undefined; - if (ns == undefined) { - bidIds.third = sharedId; - } - result.sharedid = bidIds; - utils.logInfo('SharedId: Decoded value ' + JSON.stringify(result)); - return result; - } - return sharedId; -} - -/** - * the factory to generate unique identifier based on time and current pseudorandom number - * @param {string} the current pseudorandom number generator - * @returns {function(*=): *} - */ -function factory(currPrng) { - if (!currPrng) { - currPrng = detectPrng(); - } - return function ulid(seedTime) { - if (isNaN(seedTime)) { - seedTime = Date.now(); - } - return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN, currPrng); - }; -} - -/** - * creates and logs the error message - * @function - * @param {string} error message - * @returns {Error} - */ -function createError(message) { - utils.logError(message); - const err = new Error(message); - err.source = 'sharedId'; - return err; -} - -/** - * gets a a random charcter from generated pseudorandom number - * @param {string} the generated pseudorandom number - * @returns {string} - */ -function randomChar(prng) { - let rand = Math.floor(prng() * ENCODING_LEN); - if (rand === ENCODING_LEN) { - rand = ENCODING_LEN - 1; - } - return ENCODING.charAt(rand); -} - -/** - * encodes the time based on the length - * @param now - * @param len - * @returns {string} encoded time. - */ -function encodeTime (now, len) { - if (isNaN(now)) { - throw new Error(now + ' must be a number'); - } - - if (Number.isInteger(now) === false) { - throw createError('time must be an integer'); +function queuePixelCallback(pixelUrl, id = '', callback) { + if (!pixelUrl) { + return; } - if (now > TIME_MAX) { - throw createError('cannot encode time greater than ' + TIME_MAX); - } - if (now < 0) { - throw createError('time must be positive'); - } - - if (Number.isInteger(len) === false) { - throw createError('length must be an integer'); - } - if (len < 0) { - throw createError('length must be positive'); - } - - let mod; - let str = ''; - for (; len > 0; len--) { - mod = now % ENCODING_LEN; - str = ENCODING.charAt(mod) + str; - now = (now - mod) / ENCODING_LEN; - } - return str; -} - -/** - * encodes random character - * @param len - * @param prng - * @returns {string} - */ -function encodeRandom (len, prng) { - let str = ''; - for (; len > 0; len--) { - str = randomChar(prng) + str; - } - return str; -} + // Use pubcid as a cache buster + const urlInfo = parseUrl(pixelUrl); + urlInfo.search.id = encodeURIComponent('pubcid:' + id); + const targetUrl = buildUrl(urlInfo); -/** - * detects the pseudorandom number generator and generates the random number - * @function - * @param {string} error message - * @returns {string} a random number - */ -function detectPrng(root) { - if (!root) { - root = typeof window !== 'undefined' ? window : null; - } - const browserCrypto = root && (root.crypto || root.msCrypto); - if (browserCrypto) { - return () => { - const buffer = new Uint8Array(1); - browserCrypto.getRandomValues(buffer); - return buffer[0] / 0xff; - }; - } - return () => Math.random(); + return function () { + triggerPixel(targetUrl); + }; } -/** - * Builds and returns the shared Id URL with attached consent data if applicable - * @param {Object} consentData - * @return {string} - */ -function sharedIdUrl(consentData) { - const usPrivacyString = uspDataHandler.getConsentData(); - let sharedIdUrl = ID_SVC; - if (usPrivacyString) { - sharedIdUrl = `${ID_SVC}?us_privacy=${usPrivacyString}`; - } - if (!consentData || typeof consentData.gdprApplies !== 'boolean' || !consentData.gdprApplies) return sharedIdUrl; - if (usPrivacyString) { - sharedIdUrl = `${sharedIdUrl}&gdpr=1&gdpr_consent=${consentData.consentString}` - return sharedIdUrl; - } - sharedIdUrl = `${ID_SVC}?gdpr=1&gdpr_consent=${consentData.consentString}`; - return sharedIdUrl +function hasOptedOut() { + return !!((storage.cookiesAreEnabled() && readValue(OPTOUT_NAME, COOKIE)) || + (storage.hasLocalStorage() && readValue(OPTOUT_NAME, LOCAL_STORAGE))); } -/** @type {Submodule} */ -export const sharedIdSubmodule = { +export const sharedIdSystemSubmodule = { /** * used to link submodule with config * @type {string} */ - name: MODULE_NAME, - + name: 'sharedId', + aliasName: 'pubCommonId', /** - * Vendor id of Prebid + * Vendor id of prebid * @type {Number} */ gvlid: GVLID, + /** * decode the stored id value for passing to bid requests * @function * @param {string} value - * @returns {{sharedid:{ id: string, third:string}} or undefined if value doesn't exists + * @param {SubmoduleConfig} config + * @returns {{pubcid:string}} */ - decode(value) { - return (value) ? encodeId(value) : undefined; + decode(value, config) { + if (hasOptedOut()) { + logInfo('PubCommonId decode: Has opted-out'); + return undefined; + } + logInfo(' Decoded value PubCommonId ' + value); + const idObj = {'pubcid': value}; + return idObj; }, - /** - * performs action to obtain id and return a value. + * performs action to obtain id * @function - * @param {SubmoduleConfig} [config] - * @param {ConsentData|undefined} consentData - * @returns {sharedId} + * @param {SubmoduleConfig} [config] Config object with params and storage properties + * @param {Object} consentData + * @param {string} storedId Existing pubcommon id + * @returns {IdResponse} */ - getId(config, consentData) { + getId: function (config = {}, consentData, storedId) { + if (hasOptedOut()) { + logInfo('PubCommonId: Has opted-out'); + return; + } const coppa = coppaDataHandler.getCoppa(); + if (coppa) { - utils.logInfo('SharedId: IDs not provided for coppa requests, exiting SharedId'); + logInfo('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); return; } - const resp = function (callback) { - utils.logInfo('SharedId: Sharedid doesnt exists, new cookie creation'); - ajax(sharedIdUrl(consentData), idGenerationCallback(callback), undefined, {method: 'GET', withCredentials: true}); - }; - return {callback: resp}; - }, + const {params: {create = true, pixelUrl} = {}} = config; + let newId = storedId; + if (!newId) { + try { + if (typeof window[PUB_COMMON_ID] === 'object') { + // If the page includes its own pubcid module, then save a copy of id. + newId = window[PUB_COMMON_ID].getId(); + } + } catch (e) { + } + + if (!newId) newId = (create && hasDeviceAccess()) ? generateUUID() : undefined; + } + const pixelCallback = queuePixelCallback(pixelUrl, newId); + return {id: newId, callback: getIdCallback(newId, pixelCallback)}; + }, /** - * performs actions even if the id exists and returns a value - * @param config - * @param consentData - * @param storedId - * @returns {{callback: *}} + * performs action to extend an id. There are generally two ways to extend the expiration time + * of stored id: using pixelUrl or return the id and let main user id module write it again with + * the new expiration time. + * + * PixelUrl, if defined, should point back to a first party domain endpoint. On the server + * side, there is either a plugin, or customized logic to read and write back the pubcid cookie. + * The extendId function itself should return only the callback, and not the id itself to avoid + * having the script-side overwriting server-side. This applies to both pubcid and sharedid. + * + * On the other hand, if there is no pixelUrl, then the extendId should return storedId so that + * its expiration time is updated. + * + * @function + * @param {SubmoduleParams} [config] + * @param {ConsentData|undefined} consentData + * @param {Object} storedId existing id + * @returns {IdResponse|undefined} */ - extendId(config, consentData, storedId) { + extendId: function(config = {}, consentData, storedId) { + if (hasOptedOut()) { + logInfo('PubCommonId: Has opted-out'); + return {id: undefined}; + } const coppa = coppaDataHandler.getCoppa(); if (coppa) { - utils.logInfo('SharedId: IDs not provided for coppa requests, exiting SharedId'); + logInfo('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); return; } - const configParams = (config && config.params) || {}; - utils.logInfo('SharedId: Existing shared id ' + storedId.id); - const resp = function (callback) { - const needSync = isIdSynced(configParams, storedId); - if (needSync) { - utils.logInfo('SharedId: Existing shared id ' + storedId + ' is not synced'); - const sharedIdPayload = {}; - sharedIdPayload.sharedId = storedId.id; - const payloadString = JSON.stringify(sharedIdPayload); - ajax(sharedIdUrl(consentData), existingIdCallback(storedId, callback), payloadString, {method: 'POST', withCredentials: true}); + const {params: {extend = false, pixelUrl} = {}} = config; + + if (extend) { + if (pixelUrl) { + const callback = queuePixelCallback(pixelUrl, storedId); + return {callback: callback}; + } else { + return {id: storedId}; + } + } + }, + + domainOverride: function () { + const domainElements = document.domain.split('.'); + const cookieName = `_gd${Date.now()}`; + for (let i = 0, topDomain, testCookie; i < domainElements.length; i++) { + const nextDomain = domainElements.slice(i).join('.'); + + // write test cookie + storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); + + // read test cookie to verify domain was valid + testCookie = storage.getCookie(cookieName); + + // delete test cookie + storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); + + if (testCookie === '1') { + // cookie was written successfully using test domain so the topDomain is updated + topDomain = nextDomain; + } else { + // cookie failed to write using test domain so exit by returning the topDomain + return topDomain; } - }; - return {callback: resp}; + } } + }; -// Register submodule for userId -submodule('userId', sharedIdSubmodule); +submodule('userId', sharedIdSystemSubmodule); diff --git a/modules/sharedIdSystem.md b/modules/sharedIdSystem.md deleted file mode 100644 index a4541c16c49..00000000000 --- a/modules/sharedIdSystem.md +++ /dev/null @@ -1,43 +0,0 @@ -## Shared ID User ID Submodule - -Shared ID User ID Module generates a UUID that can be utilized to improve user matching.This module enables timely synchronization which handles sharedId.org optout. This module does not require any registration. - -### Building Prebid with Shared Id Support -Your Prebid build must include the modules for both **userId** and **sharedId** submodule. Follow the build instructions for Prebid as -explained in the top level README.md file of the Prebid source tree. - -ex: $ gulp build --modules=userId,sharedIdSystem - -### Prebid Params - -Individual params may be set for the Shared ID User ID Submodule. -``` -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'sharedId', - params: { - syncTime: 60 // in seconds, default is 24 hours - }, - storage: { - name: 'sharedid', - type: 'cookie', - expires: 28 - }, - }] - } -}); -``` - -### Parameter Descriptions for the `usersync` Configuration Section -The below parameters apply only to the Shared ID User ID Module integration. - -| Params under usersync.userIds[]| Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | ID value for the Shared ID module - `"sharedId"` | `"sharedId"` | -| params | Optional | Object | Details for sharedId syncing. | | -| params.syncTime | Optional | Object | Configuration to define the frequency(in seconds) of id synchronization. By default id is synchronized every 24 hours | 60 | -| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | -| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | -| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"sharedid"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `28` | diff --git a/modules/sharethroughAnalyticsAdapter.js b/modules/sharethroughAnalyticsAdapter.js index 5147b2a4275..4f065cbca23 100644 --- a/modules/sharethroughAnalyticsAdapter.js +++ b/modules/sharethroughAnalyticsAdapter.js @@ -1,6 +1,6 @@ +import { tryAppendQueryString } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -const utils = require('../src/utils.js'); const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -42,18 +42,18 @@ var sharethroughAdapter = Object.assign(adapter( fireLoseBeacon(winningBidderCode, winningCPM, arid, type) { let loseBeaconUrl = this.STR_BEACON_HOST; - loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, 'winnerBidderCode', winningBidderCode); - loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, 'winnerCpm', winningCPM); - loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, 'arid', arid); - loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, 'type', type); + loseBeaconUrl = tryAppendQueryString(loseBeaconUrl, 'winnerBidderCode', winningBidderCode); + loseBeaconUrl = tryAppendQueryString(loseBeaconUrl, 'winnerCpm', winningCPM); + loseBeaconUrl = tryAppendQueryString(loseBeaconUrl, 'arid', arid); + loseBeaconUrl = tryAppendQueryString(loseBeaconUrl, 'type', type); loseBeaconUrl = this.appendEnvFields(loseBeaconUrl); this.fireBeacon(loseBeaconUrl); }, appendEnvFields(url) { - url = utils.tryAppendQueryString(url, 'hbVersion', '$prebid.version$'); - url = utils.tryAppendQueryString(url, 'strVersion', STR_VERSION); - url = utils.tryAppendQueryString(url, 'hbSource', 'prebid'); + url = tryAppendQueryString(url, 'hbVersion', '$prebid.version$'); + url = tryAppendQueryString(url, 'strVersion', STR_VERSION); + url = tryAppendQueryString(url, 'hbSource', 'prebid'); return url; }, diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index eef18288b17..06cc81324cf 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,116 +1,183 @@ +import { deepAccess, generateUUID, inIframe } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { createEidsArray } from './userId/eids.js'; -const VERSION = '3.3.2'; +const VERSION = '4.1.0'; const BIDDER_CODE = 'sharethrough'; -const STR_ENDPOINT = 'https://btlr.sharethrough.com/WYu2BXv1/v1'; -const DEFAULT_SIZE = [1, 1]; +const SUPPLY_ID = 'WYu2BXv1'; + +const STR_ENDPOINT = `https://btlr.sharethrough.com/universal/v1?supply_id=${SUPPLY_ID}`; // this allows stubbing of utility function that is used internally by the sharethrough adapter export const sharethroughInternal = { - b64EncodeUnicode, - handleIframe, - isLockedInFrame, - getProtocol + getProtocol, }; export const sharethroughAdapterSpec = { code: BIDDER_CODE, + supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: bid => !!bid.params.pkey && bid.bidder === BIDDER_CODE, buildRequests: (bidRequests, bidderRequest) => { - return bidRequests.map(bidRequest => { - let query = { - placement_key: bidRequest.params.pkey, - bidId: bidRequest.bidId, - consent_required: false, - instant_play_capable: canAutoPlayHTML5Video(), - hbSource: 'prebid', - hbVersion: '$prebid.version$', - strVersion: VERSION - }; - - Object.assign(query, handleUniversalIds(bidRequest)); - - const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; - query.secure = nonHttp || (sharethroughInternal.getProtocol().indexOf('https') > -1); - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) { - query.consent_string = bidderRequest.gdprConsent.consentString; - } - - if (bidderRequest && bidderRequest.gdprConsent) { - query.consent_required = !!bidderRequest.gdprConsent.gdprApplies; - } - - if (bidderRequest && bidderRequest.uspConsent) { - query.us_privacy = bidderRequest.uspConsent - } - - if (config.getConfig('coppa') === true) { - query.coppa = true + const timeout = config.getConfig('bidderTimeout'); + const firstPartyData = config.getConfig('ortb2') || {}; + + const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; + const secure = nonHttp || (sharethroughInternal.getProtocol().indexOf('https') > -1); + + const req = { + id: generateUUID(), + at: 1, + cur: ['USD'], + tmax: timeout, + site: { + domain: window.location.hostname, + page: window.location.href, + ref: deepAccess(bidderRequest, 'refererInfo.referer'), + ...firstPartyData.site, + }, + device: { + ua: navigator.userAgent, + language: navigator.language, + js: 1, + dnt: navigator.doNotTrack === '1' ? 1 : 0, + h: window.screen.height, + w: window.screen.width, + }, + regs: { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {}, + }, + source: { + ext: { + version: '$prebid.version$', + str: VERSION, + schain: bidRequests[0].schain, + }, + }, + bcat: bidRequests[0].params.bcat || [], + badv: bidRequests[0].params.badv || [], + test: 0, + }; + + req.user = nullish(firstPartyData.user, {}); + if (!req.user.ext) req.user.ext = {}; + req.user.ext.eids = userIdAsEids(bidRequests[0]); + + if (bidderRequest.gdprConsent) { + const gdprApplies = bidderRequest.gdprConsent.gdprApplies === true; + req.regs.ext.gdpr = gdprApplies ? 1 : 0; + if (gdprApplies) { + req.user.ext.consent = bidderRequest.gdprConsent.consentString; } + } - if (bidRequest.schain) { - query.schain = JSON.stringify(bidRequest.schain); - } + if (bidderRequest.uspConsent) { + req.regs.ext.us_privacy = bidderRequest.uspConsent; + } - if (bidRequest.bidfloor) { - query.bidfloor = parseFloat(bidRequest.bidfloor); - } + const imps = bidRequests.map(bidReq => { + const impression = {}; - if (bidRequest.params.badv) { - query.badv = bidRequest.params.badv; + const gpid = deepAccess(bidReq, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + impression.ext = { gpid: gpid }; } - if (bidRequest.params.bcat) { - query.bcat = bidRequest.params.bcat; + const videoRequest = deepAccess(bidReq, 'mediaTypes.video'); + + if (videoRequest) { + // default playerSize, only change this if we know width and height are properly defined in the request + let [w, h] = [640, 360]; + if (videoRequest.playerSize && videoRequest.playerSize[0] && videoRequest.playerSize[1]) { + [w, h] = videoRequest.playerSize; + } + + impression.video = { + pos: nullish(videoRequest.pos, 0), + topframe: inIframe() ? 0 : 1, + skip: nullish(videoRequest.skip, 0), + linearity: nullish(videoRequest.linearity, 1), + minduration: nullish(videoRequest.minduration, 5), + maxduration: nullish(videoRequest.maxduration, 60), + playbackmethod: videoRequest.playbackmethod || [2], + api: getVideoApi(videoRequest), + mimes: videoRequest.mimes || ['video/mp4'], + protocols: getVideoProtocols(videoRequest), + w, + h, + startdelay: nullish(videoRequest.startdelay, 0), + skipmin: nullish(videoRequest.skipmin, 0), + skipafter: nullish(videoRequest.skipafter, 0), + placement: videoRequest.context === 'instream' ? 1 : +deepAccess(videoRequest, 'placement', 4), + }; + + if (videoRequest.delivery) impression.video.delivery = videoRequest.delivery; + if (videoRequest.companiontype) impression.video.companiontype = videoRequest.companiontype; + if (videoRequest.companionad) impression.video.companionad = videoRequest.companionad; + } else { + impression.banner = { + pos: deepAccess(bidReq, 'mediaTypes.banner.pos', 0), + topframe: inIframe() ? 0 : 1, + format: bidReq.sizes.map(size => ({ w: +size[0], h: +size[1] })), + }; } - // Data that does not need to go to the server, - // but we need as part of interpretResponse() - const strData = { - skipIframeBusting: bidRequest.params.iframe, - iframeSize: bidRequest.params.iframeSize, - sizes: bidRequest.sizes + return { + id: bidReq.bidId, + tagid: String(bidReq.params.pkey), + secure: secure ? 1 : 0, + bidfloor: getBidRequestFloor(bidReq), + ...impression, }; + }).filter(imp => !!imp); + return imps.map(impression => { return { method: 'POST', url: STR_ENDPOINT, - data: query, - strData: strData + data: { + ...req, + imp: [impression], + }, }; - }) + }); }, interpretResponse: ({ body }, req) => { - if (!body || !body.creatives || !body.creatives.length) { + if (!body || !body.seatbid || body.seatbid.length === 0 || !body.seatbid[0].bid || body.seatbid[0].bid.length === 0) { return []; } - const creative = body.creatives[0]; - let size = DEFAULT_SIZE; - if (req.strData.iframeSize || req.strData.sizes.length) { - size = req.strData.iframeSize - ? req.strData.iframeSize - : getLargestSize(req.strData.sizes); - } + return body.seatbid[0].bid.map(bid => { + const response = { + requestId: bid.impid, + width: +bid.w, + height: +bid.h, + cpm: +bid.price, + creativeId: bid.crid, + dealId: bid.dealid || null, + mediaType: req.data.imp[0].video ? VIDEO : BANNER, + currency: body.cur || 'USD', + netRevenue: true, + ttl: 360, + ad: bid.adm, + nurl: bid.nurl, + meta: { + advertiserDomains: bid.adomain || [], + }, + }; - return [{ - requestId: req.data.bidId, - width: size[0], - height: size[1], - cpm: creative.cpm, - creativeId: creative.creative.creative_key, - dealId: creative.creative.deal_id, - currency: 'USD', - netRevenue: true, - ttl: 360, - ad: generateAd(body, req) - }]; + if (response.mediaType === VIDEO) { + response.ttl = 3600; + response.vastXml = bid.adm; + } + + return response; + }); }, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { @@ -131,164 +198,73 @@ export const sharethroughAdapterSpec = { }, // Empty implementation for prebid core to be able to find it - onTimeout: (data) => {}, + onTimeout: (data) => { + }, // Empty implementation for prebid core to be able to find it - onBidWon: (bid) => {}, + onBidWon: (bid) => { + }, // Empty implementation for prebid core to be able to find it - onSetTargeting: (bid) => {} + onSetTargeting: (bid) => { + }, }; -function handleUniversalIds(bidRequest) { - if (!bidRequest.userId) return {}; - - const universalIds = {}; - - const ttd = utils.deepAccess(bidRequest, 'userId.tdid'); - if (ttd) universalIds.ttduid = ttd; - - const pubc = utils.deepAccess(bidRequest, 'userId.pubcid') || utils.deepAccess(bidRequest, 'crumbs.pubcid'); - if (pubc) universalIds.pubcid = pubc; - - const idl = utils.deepAccess(bidRequest, 'userId.idl_env'); - if (idl) universalIds.idluid = idl; - - const id5 = utils.deepAccess(bidRequest, 'userId.id5id.uid'); - if (id5) { - universalIds.id5uid = { id: id5 }; - const id5link = utils.deepAccess(bidRequest, 'userId.id5id.ext.linkType'); - if (id5link) universalIds.id5uid.linkType = id5link; - } - - const lipb = utils.deepAccess(bidRequest, 'userId.lipb.lipbid'); - if (lipb) universalIds.liuid = lipb; - - const shd = utils.deepAccess(bidRequest, 'userId.sharedid'); - if (shd) universalIds.shduid = shd; // object with keys: id & third - - return universalIds; -} - -function getLargestSize(sizes) { - function area(size) { - return size[0] * size[1]; +function getVideoApi({ api }) { + let defaultValue = [2]; + if (api && Array.isArray(api) && api.length > 0) { + return api; + } else { + return defaultValue; } - - return sizes.reduce((prev, current) => { - if (area(current) > area(prev)) { - return current - } else { - return prev - } - }); } -function generateAd(body, req) { - const strRespId = `str_response_${req.data.bidId}`; - - let adMarkup = ` -
-
- - `; - - if (req.strData.skipIframeBusting) { - // Don't break out of iframe - adMarkup = adMarkup + ``; +function getVideoProtocols({ protocols }) { + let defaultValue = [2, 3, 5, 6, 7, 8]; + if (protocols && Array.isArray(protocols) && protocols.length > 0) { + return protocols; } else { - // Add logic to the markup that detects whether or not in top level document is accessible - // this logic will deploy sfp.js and/or iframe buster script(s) as appropriate - adMarkup = adMarkup + ` - - `; + return defaultValue; } - - return adMarkup; } -function handleIframe () { - // only load iframe buster JS if we can access the top level document - // if we are 'locked in' to this frame then no point trying to bust out: we may as well render in the frame instead - var iframeBusterLoaded = false; - if (!window.lockedInFrame) { - var sfpIframeBusterJs = document.createElement('script'); - sfpIframeBusterJs.src = 'https://native.sharethrough.com/assets/sfp-set-targeting.js'; - sfpIframeBusterJs.type = 'text/javascript'; - try { - window.document.getElementsByTagName('body')[0].appendChild(sfpIframeBusterJs); - iframeBusterLoaded = true; - } catch (e) { - utils.logError('Trouble writing frame buster script, error details:', e); - } - } - - var clientJsLoaded = (!iframeBusterLoaded) ? !!(window.STR && window.STR.Tag) : !!(window.top.STR && window.top.STR.Tag); - if (!clientJsLoaded) { - var sfpJs = document.createElement('script'); - sfpJs.src = 'https://native.sharethrough.com/assets/sfp.js'; - sfpJs.type = 'text/javascript'; - - // only add sfp js to window.top if iframe busting successfully loaded; otherwise, add to iframe - try { - if (iframeBusterLoaded) { - window.top.document.getElementsByTagName('body')[0].appendChild(sfpJs); - } else { - window.document.getElementsByTagName('body')[0].appendChild(sfpJs); - } - } catch (e) { - utils.logError('Trouble writing sfp script, error details:', e); +function getBidRequestFloor(bid) { + let floor = null; + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: bid.mediaTypes && bid.mediaTypes.video ? 'video' : 'banner', + size: bid.sizes.map(size => ({ w: size[0], h: size[1] })), + }); + if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); } } + return floor !== null ? floor : bid.params.floor; } -// determines if we are capable of busting out of the iframe we are in -// if we catch a DOMException when trying to access top-level document, it means we're stuck in the frame we're in -function isLockedInFrame () { - window.lockedInFrame = false; - try { - window.lockedInFrame = !window.top.document; - } catch (e) { - window.lockedInFrame = (e instanceof DOMException); +function userIdAsEids(bidRequest) { + const eids = createEidsArray(deepAccess(bidRequest, 'userId')) || []; + + const flocData = deepAccess(bidRequest, 'userId.flocId'); + const isFlocIdValid = flocData && flocData.id && flocData.version; + if (isFlocIdValid) { + eids.push({ + source: 'chrome.com', + uids: [{ id: flocData.id, atype: 1, ext: { ver: flocData.version } }], + }); } -} -// See https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem -function b64EncodeUnicode(str) { - return btoa( - encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, - function toSolidBytes(match, p1) { - return String.fromCharCode('0x' + p1); - })); + return eids; } -function canAutoPlayHTML5Video() { - const userAgent = navigator.userAgent; - if (!userAgent) return false; - - const isAndroid = /Android/i.test(userAgent); - const isiOS = /iPhone|iPad|iPod/i.test(userAgent); - const chromeVersion = parseInt((/Chrome\/([0-9]+)/.exec(userAgent) || [0, 0])[1]); - const chromeiOSVersion = parseInt((/CriOS\/([0-9]+)/.exec(userAgent) || [0, 0])[1]); - const safariVersion = parseInt((/Version\/([0-9]+)/.exec(userAgent) || [0, 0])[1]); - - if ( - (isAndroid && chromeVersion >= 53) || - (isiOS && (safariVersion >= 10 || chromeiOSVersion >= 53)) || - !(isAndroid || isiOS) - ) { - return true; - } else { - return false; - } +function getProtocol() { + return window.location.protocol; } -function getProtocol() { - return document.location.protocol; +// stub for ?? operator +function nullish(input, def) { + return input === null || input === undefined ? def : input; } registerBidder(sharethroughAdapterSpec); diff --git a/modules/sharethroughBidAdapter.md b/modules/sharethroughBidAdapter.md index 396b8164577..218ef353d4e 100644 --- a/modules/sharethroughBidAdapter.md +++ b/modules/sharethroughBidAdapter.md @@ -23,22 +23,74 @@ Module that connects to Sharethrough's demand sources // REQUIRED - The placement key pkey: 'LuB3vxGGFrBZJa6tifXW4xgK', - // OPTIONAL - Render Sharethrough creative in an iframe, defaults to false - iframe: true, - - // OPTIONAL - If iframeSize is provided, we'll use this size for the iframe - // otherwise we'll grab the largest size from the sizes array - // This is ignored if iframe: false - iframeSize: [250, 250], - // OPTIONAL - Blocked Advertiser Domains badv: ['domain1.com', 'domain2.com'], // OPTIONAL - Blocked Categories (IAB codes) bcat: ['IAB1-1', 'IAB1-2'], + + // OPTIONAL - default bid floor, if not specified in bid request (USD) + floor: 0.1, } } ] } ]; ``` + +# Sample Instream Video Ad Unit: For Publishers +``` +var adVideoAdUnits = [ +{ + code: 'test-div-video', + mediaTypes: { + video: { + // CANNOT be 'outstream' + context: 'instream', + placement: 1, + delivery: 1, + companiontype: 'companion type', + companionad: 'companion ad', + // default values shown below this point + pos: 0, + skip: 0, + linearity: 1, + minduration: 5, + maxduration: 60, + playbackmethod: [2], + api: [2], + mimes: ['video/mp4'], + protocols: [2, 3, 5, 6, 7, 8], + playerSize: [640, 360], + startdelay: 0, + skipmin: 0, + skipafter: 0, + }, + }, + bids: [{ + bidder: 'sharethrough', + params: { + pkey: 'pkey1' + } + }] +}] +``` + +# Sample Banner Ad Unit: For Publishers +``` +var adUnits = [ +{ + code: 'test-div-video', + mediaTypes: { + banner: { + pos: 0, // default + }, + }, + bids: [{ + bidder: 'sharethrough', + params: { + pkey: 'pkey1' + } + }] +}] +``` diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js deleted file mode 100644 index d5734d23fdc..00000000000 --- a/modules/shinezBidAdapter.js +++ /dev/null @@ -1,83 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'shinez'; -const BIDDER_SHORT_CODE = 'shz'; -const ADAPTER_VERSION = '1.0.0'; - -const TARGET_URL = 'https://shinez-ssp.shinez.workers.dev/prebid'; - -export const spec = { - code: BIDDER_CODE, - version: ADAPTER_VERSION, - aliases: { - code: BIDDER_SHORT_CODE - }, - supportedMediaTypes: [ BANNER ], - isBidRequestValid: isBidRequestValid, - buildRequests: buildRequests, - interpretResponse: interpretResponse, -}; - -export const internal = { - TARGET_URL -} - -function isBidRequestValid(bid) { - return !!(bid && bid.params && - bid.params.placementId && typeof bid.params.placementId === 'string' && - (bid.params.unit == null || (typeof bid.params.unit === 'string' && bid.params.unit.length > 0)) - ); -} - -function buildRequests(validBidRequests, bidderRequest) { - const utcOffset = (new Date()).getTimezoneOffset(); - const data = []; - validBidRequests.forEach(function(bidRequest) { - data.push(_buildServerBidRequest(bidRequest, bidderRequest, utcOffset)); - }); - const request = { - method: 'POST', - url: TARGET_URL, - data: data - }; - return request; -} - -function interpretResponse(serverResponse, request) { - const bids = []; - serverResponse.body.forEach(function(serverBid) { - bids.push(_convertServerBid(serverBid)); - }); - return bids; -} - -function _buildServerBidRequest(bidRequest, bidderRequest, utcOffset) { - return { - bidId: bidRequest.bidId, - transactionId: bidRequest.transactionId, - crumbs: bidRequest.crumbs, - mediaTypes: bidRequest.mediaTypes, - refererInfo: bidderRequest.refererInfo, - placementId: bidRequest.params.placementId, - utcOffset: utcOffset, - adUnitCode: bidRequest.adUnitCode, - unit: bidRequest.params.unit - } -} - -function _convertServerBid(response) { - return { - requestId: response.bidId, - cpm: response.cpm, - currency: response.currency, - width: response.width, - height: response.height, - ad: response.ad, - ttl: response.ttl, - creativeId: response.creativeId, - netRevenue: response.netRevenue - }; -} - -registerBidder(spec); diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index d0eb8c6a589..4c8fb812edc 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { deepAccess, getBidIdParameter, getWindowTop, logError } from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -32,18 +32,18 @@ export const spec = { let adUnits = []; const pageURL = validBidRequests[0].params.contentPageUrl || bidderRequest.refererInfo.referer; const isStage = !!validBidRequests[0].params.stage; - const isOutstream = utils.deepAccess(validBidRequests[0], 'mediaTypes.video.context') === 'outstream'; - const isCustomRender = utils.deepAccess(validBidRequests[0], 'params.outstreamOptions.customRender'); - const isNodeRender = utils.deepAccess(validBidRequests[0], 'params.outstreamOptions.slot') || utils.deepAccess(validBidRequests[0], 'params.outstreamOptions.iframe'); - const isNativeRender = utils.deepAccess(validBidRequests[0], 'renderer'); - const outstreamOptions = utils.deepAccess(validBidRequests[0], 'params.outstreamOptions'); + const isOutstream = deepAccess(validBidRequests[0], 'mediaTypes.video.context') === 'outstream'; + const isCustomRender = deepAccess(validBidRequests[0], 'params.outstreamOptions.customRender'); + const isNodeRender = deepAccess(validBidRequests[0], 'params.outstreamOptions.slot') || deepAccess(validBidRequests[0], 'params.outstreamOptions.iframe'); + const isNativeRender = deepAccess(validBidRequests[0], 'renderer'); + const outstreamOptions = deepAccess(validBidRequests[0], 'params.outstreamOptions'); const isBanner = !!validBidRequests[0].mediaTypes.banner || (isOutstream && !(isCustomRender || isNativeRender || isNodeRender)); const defaultSchain = validBidRequests[0].schain || {}; validBidRequests.forEach((bid) => { const videoSizes = getVideoSizes(bid); const bannerSizes = getBannerSizes(bid); - const vpaidMode = utils.getBidIdParameter('vpaidMode', bid.params); + const vpaidMode = getBidIdParameter('vpaidMode', bid.params); const makeBids = (type, size) => { let context = ''; @@ -52,7 +52,7 @@ export const spec = { if (type === BANNER) { streamType = 5; } else { - context = utils.deepAccess(bid, 'mediaTypes.video.context'); + context = deepAccess(bid, 'mediaTypes.video.context'); if (vpaidMode && context === 'instream') { streamType = 1; } @@ -61,15 +61,24 @@ export const spec = { } } + const consentData = bidderRequest.gdprConsent || {}; + + const gdprConsent = { + apiVersion: consentData.apiVersion || 2, + gdprApplies: consentData.gdprApplies || 0, + consentString: consentData.consentString || '', + } + return { type: streamType, + adUnitCode: bid.adUnitCode, bidId: bid.bidId, mediaType: type, context: context, - playerId: utils.getBidIdParameter('playerId', bid.params), + playerId: getBidIdParameter('playerId', bid.params), auctionId: bidderRequest.auctionId, bidderCode: BIDDER_CODE, - gdprConsent: bidderRequest.gdprConsent, + gdprConsent: gdprConsent, start: +new Date(), timeout: 3000, size: { @@ -100,7 +109,7 @@ export const spec = { 'adapterVersion': 2, 'pageURL': encodeURIComponent(pageURL), 'vastCacheEnabled': (!!config.getConfig('cache') && !isBanner && !outstreamOptions) || false, - 'isDesktop': utils.getWindowTop().document.documentElement.clientWidth > 700, + 'isDesktop': getWindowTop().document.documentElement.clientWidth > 700, 'xmlAndTag': !!(isOutstream && isCustomRender) || false, 'stage': isStage || undefined }, @@ -159,6 +168,7 @@ function createBids(bidRes, reqData) { let bidUnit = {}; bidUnit.cpm = bid.cpm; bidUnit.requestId = bid.bidId; + bidUnit.adUnitCode = reqBid.adUnitCode; bidUnit.currency = bid.currency; bidUnit.mediaType = bid.mediaType || VIDEO; bidUnit.ttl = TTL; @@ -166,6 +176,9 @@ function createBids(bidRes, reqData) { bidUnit.netRevenue = true; bidUnit.width = bid.size.width; bidUnit.height = bid.size.height; + bidUnit.meta = { + advertiserDomains: bid.adomain || [] + }; if (bid.vastXml) { bidUnit.vastXml = bid.vastXml; bidUnit.adResponse = { @@ -180,7 +193,8 @@ function createBids(bidRes, reqData) { } else if (bid.context === 'outstream') { const renderer = Renderer.install({ id: bid.bidId, - url: '//', + url: 'https://static.showheroes.com/renderer.js', + adUnitCode: reqBid.adUnitCode, config: { playerId: reqBid.playerId, width: bid.size.width, @@ -189,9 +203,9 @@ function createBids(bidRes, reqData) { vastXml: bid.vastXml, debug: reqData.debug, isStage: !!reqData.meta.stage, - customRender: utils.getBidIdParameter('customRender', currentBidParams.outstreamOptions), - slot: utils.getBidIdParameter('slot', currentBidParams.outstreamOptions), - iframe: utils.getBidIdParameter('iframe', currentBidParams.outstreamOptions), + customRender: getBidIdParameter('customRender', currentBidParams.outstreamOptions), + slot: getBidIdParameter('slot', currentBidParams.outstreamOptions), + iframe: getBidIdParameter('iframe', currentBidParams.outstreamOptions), } }); renderer.setRender(outstreamRender); @@ -209,7 +223,7 @@ function outstreamRender(bid) { bid.renderer.config.customRender(bid, embedCode); } else { try { - const inIframe = utils.getBidIdParameter('iframe', bid.renderer.config); + const inIframe = getBidIdParameter('iframe', bid.renderer.config); if (inIframe && window.document.getElementById(inIframe).nodeName === 'IFRAME') { const iframe = window.document.getElementById(inIframe); let framedoc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document); @@ -217,20 +231,20 @@ function outstreamRender(bid) { return; } - const slot = utils.getBidIdParameter('slot', bid.renderer.config) || bid.adUnitCode; + const slot = getBidIdParameter('slot', bid.renderer.config) || bid.adUnitCode; if (slot && window.document.getElementById(slot)) { window.document.getElementById(slot).appendChild(embedCode); } else if (slot) { - utils.logError('[ShowHeroes][renderer] Error: spot not found'); + logError('[ShowHeroes][renderer] Error: spot not found'); } } catch (err) { - utils.logError('[ShowHeroes][renderer] Error:' + err.message) + logError('[ShowHeroes][renderer] Error:' + err.message) } } } function createOutstreamEmbedCode(bid) { - const isStage = utils.getBidIdParameter('isStage', bid.renderer.config); + const isStage = getBidIdParameter('isStage', bid.renderer.config); const urls = getEnvURLs(isStage); const fragment = window.document.createDocumentFragment(); @@ -242,9 +256,9 @@ function createOutstreamEmbedCode(bid) { const spot = window.document.createElement('div'); spot.setAttribute('class', 'showheroes-spot'); - spot.setAttribute('data-player', utils.getBidIdParameter('playerId', bid.renderer.config)); - spot.setAttribute('data-debug', utils.getBidIdParameter('debug', bid.renderer.config)); - spot.setAttribute('data-ad-vast-tag', utils.getBidIdParameter('vastUrl', bid.renderer.config)); + spot.setAttribute('data-player', getBidIdParameter('playerId', bid.renderer.config)); + spot.setAttribute('data-debug', getBidIdParameter('debug', bid.renderer.config)); + spot.setAttribute('data-ad-vast-tag', getBidIdParameter('vastUrl', bid.renderer.config)); spot.setAttribute('data-stream-type', 'outstream'); fragment.appendChild(spot); @@ -272,11 +286,11 @@ function getBannerHtml (bid, reqBid, reqData) { } function getVideoSizes(bidRequest) { - return formatSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') || []); + return formatSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize') || []); } function getBannerSizes(bidRequest) { - return formatSizes(utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || []); + return formatSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes') || []); } function formatSizes(sizes) { diff --git a/modules/sigmoidAnalyticsAdapter.js b/modules/sigmoidAnalyticsAdapter.js index 303fbbc8995..da0ca9e38e5 100644 --- a/modules/sigmoidAnalyticsAdapter.js +++ b/modules/sigmoidAnalyticsAdapter.js @@ -5,11 +5,10 @@ import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; +import { generateUUID, logInfo, logError } from '../src/utils.js'; const storage = getStorageManager(); -const utils = require('../src/utils.js'); - const url = 'https://kinesis.us-east-1.amazonaws.com/'; const analyticsType = 'endpoint'; @@ -56,7 +55,7 @@ function buildSessionIdTimeoutLocalStorageKey() { function updateSessionId() { if (isSessionIdTimeoutExpired()) { - let newSessionId = utils.generateUUID(); + let newSessionId = generateUUID(); storage.setDataInLocalStorage(buildSessionIdLocalStorageKey(), newSessionId); } initOptions.sessionId = getSessionId(); @@ -206,7 +205,7 @@ sigmoidAdapter.originEnableAnalytics = sigmoidAdapter.enableAnalytics; sigmoidAdapter.enableAnalytics = function (config) { initOptions = config.options; initOptions.utmTagData = this.buildUtmTagData(); - utils.logInfo('Sigmoid Analytics enabled with config', initOptions); + logInfo('Sigmoid Analytics enabled with config', initOptions); sigmoidAdapter.originEnableAnalytics(config); }; @@ -246,7 +245,7 @@ function send(eventType, data, sendDataType) { AWS.config.credentials.get(function(err) { // attach event listener if (err) { - utils.logError(err); + logError(err); return; } // create kinesis service object diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 373468b2f14..344357bcb62 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -7,7 +7,7 @@ * @requires module:modules/realTimeData */ import {getGlobal} from '../src/prebidGlobal.js'; -import * as utils from '../src/utils.js'; +import { deepAccess, logError, deepEqual, deepSetValue, isEmpty, mergeDeep } from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {ajax} from '../src/ajax.js'; import findIndex from 'core-js-pure/features/array/find-index.js'; @@ -44,7 +44,7 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, } // default global endpoint is cookie-based if no rules falls into cookieless or consent has been given or GDPR doesn't apply - if (!sirdataDomain || !gdprApplies || (utils.deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[53] && userConsent.gdpr.vendorData.purpose.consents[1] && userConsent.gdpr.vendorData.purpose.consents[4])) { + if (!sirdataDomain || !gdprApplies || (deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[53] && userConsent.gdpr.vendorData.purpose.consents[1] && userConsent.gdpr.vendorData.purpose.consents[4])) { sirdataDomain = 'sddan.com'; sendWithCredentials = true; } @@ -64,7 +64,7 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, } } catch (e) { onDone(); - utils.logError('unable to parse Sirdata data' + e); + logError('unable to parse Sirdata data' + e); } } else if (req.status === 204) { onDone(); @@ -72,7 +72,7 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, }, error: function () { onDone(); - utils.logError('unable to get Sirdata data'); + logError('unable to get Sirdata data'); } }, null, @@ -89,18 +89,18 @@ export function setGlobalOrtb2(segments, categories) { try { let addOrtb2 = {}; let testGlobal = getGlobal().getConfig('ortb2') || {}; - if (!utils.deepAccess(testGlobal, 'user.ext.data.sd_rtd') || !utils.deepEqual(testGlobal.user.ext.data.sd_rtd, segments)) { - utils.deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + if (!deepAccess(testGlobal, 'user.ext.data.sd_rtd') || !deepEqual(testGlobal.user.ext.data.sd_rtd, segments)) { + deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); } - if (!utils.deepAccess(testGlobal, 'site.ext.data.sd_rtd') || !utils.deepEqual(testGlobal.site.ext.data.sd_rtd, categories)) { - utils.deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); + if (!deepAccess(testGlobal, 'site.ext.data.sd_rtd') || !deepEqual(testGlobal.site.ext.data.sd_rtd, categories)) { + deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); } - if (!utils.isEmpty(addOrtb2)) { - let ortb2 = {ortb2: utils.mergeDeep({}, testGlobal, addOrtb2)}; + if (!isEmpty(addOrtb2)) { + let ortb2 = {ortb2: mergeDeep({}, testGlobal, addOrtb2)}; getGlobal().setConfig(ortb2); } } catch (e) { - utils.logError(e) + logError(e) } return true; @@ -109,19 +109,19 @@ export function setGlobalOrtb2(segments, categories) { export function setBidderOrtb2(bidder, segments, categories) { try { let addOrtb2 = {}; - let testBidder = utils.deepAccess(config.getBidderConfig(), bidder + '.ortb2') || {}; - if (!utils.deepAccess(testBidder, 'user.ext.data.sd_rtd') || !utils.deepEqual(testBidder.user.ext.data.sd_rtd, segments)) { - utils.deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + let testBidder = deepAccess(config.getBidderConfig(), bidder + '.ortb2') || {}; + if (!deepAccess(testBidder, 'user.ext.data.sd_rtd') || !deepEqual(testBidder.user.ext.data.sd_rtd, segments)) { + deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); } - if (!utils.deepAccess(testBidder, 'site.ext.data.sd_rtd') || !utils.deepEqual(testBidder.site.ext.data.sd_rtd, categories)) { - utils.deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); + if (!deepAccess(testBidder, 'site.ext.data.sd_rtd') || !deepEqual(testBidder.site.ext.data.sd_rtd, categories)) { + deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); } - if (!utils.isEmpty(addOrtb2)) { - let ortb2 = {ortb2: utils.mergeDeep({}, testBidder, addOrtb2)}; + if (!isEmpty(addOrtb2)) { + let ortb2 = {ortb2: mergeDeep({}, testBidder, addOrtb2)}; getGlobal().setBidderConfig({ bidders: [bidder], config: ortb2 }); } } catch (e) { - utils.logError(e) + logError(e) } return true; @@ -132,7 +132,7 @@ export function loadCustomFunction (todo, adUnit, list, data, bid) { if (typeof todo == 'function') { todo(adUnit, list, data, bid); } - } catch (e) { utils.logError(e); } + } catch (e) { logError(e); } return true; } @@ -148,14 +148,14 @@ export function getSegAndCatsArray(data, minScore) { } } } - } catch (e) { utils.logError(e); } + } catch (e) { logError(e); } try { if (data && data.segments) { for (let segId in data.segments) { sirdataData.segments.push(data.segments[segId].toString()); } } - } catch (e) { utils.logError(e); } + } catch (e) { logError(e); } return sirdataData; } @@ -165,7 +165,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { const globalMinScore = moduleConfig.params.hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.contextualMinRelevancyScore : 30; var sirdataData = getSegAndCatsArray(data, globalMinScore); - if (!sirdataData || (sirdataData.segments.length < 1 && sirdataData.categories.length < 1)) { utils.logError('no cats'); onDone(); return adUnits; } + if (!sirdataData || (sirdataData.segments.length < 1 && sirdataData.categories.length < 1)) { logError('no cats'); onDone(); return adUnits; } const sirdataList = sirdataData.segments.concat(sirdataData.categories); @@ -191,7 +191,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { n.setTargeting('sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories)); } }) - } catch (e) { utils.logError(e); } + } catch (e) { logError(e); } } // Bid targeting level for FPD non-generic biders @@ -199,8 +199,8 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { var indexFound = false; adUnits.forEach(adUnit => { - if (!biddersParamsExist && !utils.deepAccess(adUnit, 'ortb2Imp.ext.data.sd_rtd')) { - utils.deepSetValue(adUnit, 'ortb2Imp.ext.data.sd_rtd', sirdataList); + if (!biddersParamsExist && !deepAccess(adUnit, 'ortb2Imp.ext.data.sd_rtd')) { + deepSetValue(adUnit, 'ortb2Imp.ext.data.sd_rtd', sirdataList); } adUnit.hasOwnProperty('bids') && adUnit.bids.forEach(bid => { @@ -236,7 +236,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); } else { - utils.deepSetValue(bid, 'params.keywords.sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories)); + deepSetValue(bid, 'params.keywords.sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories)); } break; @@ -259,7 +259,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { target.push('sd_rtd=' + entry); } }); - utils.deepSetValue(bid, 'params.target', target.join(';')); + deepSetValue(bid, 'params.target', target.join(';')); } break; @@ -313,7 +313,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); } else { - utils.deepSetValue(bid, 'ortb2.user.ext.data', {segments: sirdataData.segments.concat(curationData.segments), contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[curationId].contextual_categories}}); + deepSetValue(bid, 'ortb2.user.ext.data', {segments: sirdataData.segments.concat(curationData.segments), contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[curationId].contextual_categories}}); } break; @@ -372,16 +372,16 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { default: if (!biddersParamsExist || indexFound) { - if (!utils.deepAccess(bid, 'ortb2.site.ext.data.sd_rtd')) { - utils.deepSetValue(bid, 'ortb2.site.ext.data.sd_rtd', sirdataData.categories); + if (!deepAccess(bid, 'ortb2.site.ext.data.sd_rtd')) { + deepSetValue(bid, 'ortb2.site.ext.data.sd_rtd', sirdataData.categories); } - if (!utils.deepAccess(bid, 'ortb2.user.ext.data.sd_rtd')) { - utils.deepSetValue(bid, 'ortb2.user.ext.data.sd_rtd', sirdataData.segments); + if (!deepAccess(bid, 'ortb2.user.ext.data.sd_rtd')) { + deepSetValue(bid, 'ortb2.user.ext.data.sd_rtd', sirdataData.segments); } } } } - } catch (e) { utils.logError(e) } + } catch (e) { logError(e) } }) }); diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index ffd242e57ac..b3a6d32b2d8 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -4,8 +4,7 @@ * rendering. Read full API documentation on Prebid.org, http://prebid.org/dev-docs/modules/sizeMappingV2.html */ -import * as utils from '../src/utils.js'; -import { processNativeAdUnitParams } from '../src/native.js'; +import { isArray, logError, isArrayOfNums, deepClone, logWarn, getWindowTop, deepEqual, logInfo, isValidMediaTypes, deepAccess, getDefinedParams, getUniqueIdentifierStr, flatten } from '../src/utils.js'; import { adunitCounter } from '../src/adUnits.js'; import includes from 'core-js-pure/features/array/includes.js'; import { getHook } from '../src/hook.js'; @@ -66,7 +65,7 @@ export function isUsingNewSizeMapping(adUnits) { }); // checks for the presence of sizeConfig property at the adUnit.bids[].bidder object - adUnit.bids && utils.isArray(adUnit.bids) && adUnit.bids.forEach(bidder => { + adUnit.bids && isArray(adUnit.bids) && adUnit.bids.forEach(bidder => { if (bidder.sizeConfig) { if (isUsingSizeMappingBool === false) { isUsingSizeMappingBool = true; @@ -108,7 +107,7 @@ export function checkAdUnitSetupHook(adUnits) { If they do not, return 'false'. */ if (!(includes(keys, 'minViewPort') && includes(keys, propertyName))) { - utils.logError(`Ad unit ${adUnitCode}: Missing required property 'minViewPort' or 'sizes' from 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + logError(`Ad unit ${adUnitCode}: Missing required property 'minViewPort' or 'sizes' from 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); isValid = false; return; } @@ -117,8 +116,8 @@ export function checkAdUnitSetupHook(adUnits) { Verify that 'config.minViewPort' property is in [width, height] format. If not, return false. */ - if (!utils.isArrayOfNums(config.minViewPort, 2)) { - utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'minViewPort' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + if (!isArrayOfNums(config.minViewPort, 2)) { + logError(`Ad unit ${adUnitCode}: Invalid declaration of 'minViewPort' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); isValid = false return; } @@ -141,7 +140,7 @@ export function checkAdUnitSetupHook(adUnits) { showError = true; } if (showError) { - utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of '${propertyName}' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + logError(`Ad unit ${adUnitCode}: Invalid declaration of '${propertyName}' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); return; } } @@ -152,13 +151,13 @@ export function checkAdUnitSetupHook(adUnits) { */ if (mediaType === 'native') { if (typeof config[propertyName] !== 'boolean') { - utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'active' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + logError(`Ad unit ${adUnitCode}: Invalid declaration of 'active' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); isValid = false; } } }); } else { - utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'sizeConfig' in 'mediaTypes.${mediaType}.sizeConfig'. ${conditionalLogMessages[mediaType]}`); + logError(`Ad unit ${adUnitCode}: Invalid declaration of 'sizeConfig' in 'mediaTypes.${mediaType}.sizeConfig'. ${conditionalLogMessages[mediaType]}`); isValid = false; return isValid; } @@ -172,13 +171,13 @@ export function checkAdUnitSetupHook(adUnits) { const mediaTypes = adUnit.mediaTypes; let validatedBanner, validatedVideo, validatedNative; - if (!bids || !utils.isArray(bids)) { - utils.logError(`Detected adUnit.code '${adUnit.code}' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.`); + if (!bids || !isArray(bids)) { + logError(`Detected adUnit.code '${adUnit.code}' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.`); return; } if (!mediaTypes || Object.keys(mediaTypes).length === 0) { - utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); + logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); return; } if (mediaTypes.banner) { @@ -187,7 +186,7 @@ export function checkAdUnitSetupHook(adUnits) { validatedBanner = adUnitSetupChecks.validateBannerMediaType(adUnit); } else if (mediaTypes.banner.sizeConfig) { // Ad unit is using the 'sizeConfig' property, 'mediaTypes.banner.sizeConfig'. Apply the new checks! - validatedBanner = utils.deepClone(adUnit); + validatedBanner = deepClone(adUnit); const isBannerValid = validateSizeConfig('banner', mediaTypes.banner.sizeConfig, adUnit.code); if (!isBannerValid) { delete validatedBanner.mediaTypes.banner; @@ -204,8 +203,8 @@ export function checkAdUnitSetupHook(adUnits) { } } else { // Ad unit is invalid since it's mediaType property does not have either 'sizes' or 'sizeConfig' declared. - utils.logError(`Ad unit ${adUnit.code}: 'mediaTypes.banner' does not contain either 'sizes' or 'sizeConfig' property. Removing 'mediaTypes.banner' from ad unit.`); - validatedBanner = utils.deepClone(adUnit); + logError(`Ad unit ${adUnit.code}: 'mediaTypes.banner' does not contain either 'sizes' or 'sizeConfig' property. Removing 'mediaTypes.banner' from ad unit.`); + validatedBanner = deepClone(adUnit); delete validatedBanner.mediaTypes.banner; } } @@ -216,7 +215,7 @@ export function checkAdUnitSetupHook(adUnits) { validatedVideo = validatedBanner ? adUnitSetupChecks.validateVideoMediaType(validatedBanner) : adUnitSetupChecks.validateVideoMediaType(adUnit); } else if (mediaTypes.video.sizeConfig) { // Ad unit is using the 'sizeConfig' property, 'mediaTypes.video.sizeConfig'. Apply the new checks! - validatedVideo = validatedBanner || utils.deepClone(adUnit); + validatedVideo = validatedBanner || deepClone(adUnit); const isVideoValid = validateSizeConfig('video', mediaTypes.video.sizeConfig, adUnit.code); if (!isVideoValid) { delete validatedVideo.mediaTypes.video.sizeConfig; @@ -274,7 +273,7 @@ export function checkBidderSizeConfigFormat(sizeConfig) { const keys = Object.keys(config); if ((includes(keys, 'minViewPort') && includes(keys, 'relevantMediaTypes')) && - utils.isArrayOfNums(config.minViewPort, 2) && + isArrayOfNums(config.minViewPort, 2) && Array.isArray(config.relevantMediaTypes) && config.relevantMediaTypes.length > 0 && (config.relevantMediaTypes.length > 1 ? (config.relevantMediaTypes.every(mt => (includes(['banner', 'video', 'native'], mt)))) @@ -322,14 +321,14 @@ export function isLabelActivated(bidOrAdUnit, activeLabels, adUnitCode, adUnitIn let labelOperator; const labelsFound = Object.keys(bidOrAdUnit).filter(prop => prop === 'labelAny' || prop === 'labelAll'); if (labelsFound && labelsFound.length > 1) { - utils.logWarn(`Size Mapping V2:: ${(bidOrAdUnit.code) + logWarn(`Size Mapping V2:: ${(bidOrAdUnit.code) ? (`Ad Unit: ${bidOrAdUnit.code}(${adUnitInstance}) => Ad unit has multiple label operators. Using the first declared operator: ${labelsFound[0]}`) : (`Ad Unit: ${adUnitCode}(${adUnitInstance}), Bidder: ${bidOrAdUnit.bidder} => Bidder has multiple label operators. Using the first declared operator: ${labelsFound[0]}`)}`); } labelOperator = labelsFound[0]; if (labelOperator && !activeLabels) { - utils.logWarn(`Size Mapping V2:: ${(bidOrAdUnit.code) + logWarn(`Size Mapping V2:: ${(bidOrAdUnit.code) ? (`Ad Unit: ${bidOrAdUnit.code}(${adUnitInstance}) => Found '${labelOperator}' on ad unit, but 'labels' is not set. Did you pass 'labels' to pbjs.requestBids() ?`) : (`Ad Unit: ${adUnitCode}(${adUnitInstance}), Bidder: ${bidOrAdUnit.bidder} => Found '${labelOperator}' on bidder, but 'labels' is not set. Did you pass 'labels' to pbjs.requestBids() ?`)}`); return true; @@ -337,13 +336,13 @@ export function isLabelActivated(bidOrAdUnit, activeLabels, adUnitCode, adUnitIn if (labelOperator === 'labelAll' && Array.isArray(bidOrAdUnit[labelOperator])) { if (bidOrAdUnit.labelAll.length === 0) { - utils.logWarn(`Size Mapping V2:: Ad Unit: ${bidOrAdUnit.code}(${adUnitInstance}) => Ad unit has declared property 'labelAll' with an empty array.`); + logWarn(`Size Mapping V2:: Ad Unit: ${bidOrAdUnit.code}(${adUnitInstance}) => Ad unit has declared property 'labelAll' with an empty array.`); return true; } return bidOrAdUnit.labelAll.every(label => includes(activeLabels, label)); } else if (labelOperator === 'labelAny' && Array.isArray(bidOrAdUnit[labelOperator])) { if (bidOrAdUnit.labelAny.length === 0) { - utils.logWarn(`Size Mapping V2:: Ad Unit: ${bidOrAdUnit.code}(${adUnitInstance}) => Ad unit has declared property 'labelAny' with an empty array.`); + logWarn(`Size Mapping V2:: Ad Unit: ${bidOrAdUnit.code}(${adUnitInstance}) => Ad unit has declared property 'labelAny' with an empty array.`); return true; } return bidOrAdUnit.labelAny.some(label => includes(activeLabels, label)); @@ -363,7 +362,7 @@ export function getFilteredMediaTypes(mediaTypes) { activeViewportHeight, transformedMediaTypes; - transformedMediaTypes = utils.deepClone(mediaTypes); + transformedMediaTypes = deepClone(mediaTypes); let activeSizeBucket = { banner: undefined, @@ -372,10 +371,10 @@ export function getFilteredMediaTypes(mediaTypes) { } try { - activeViewportWidth = utils.getWindowTop().innerWidth; - activeViewportHeight = utils.getWindowTop().innerHeight; + activeViewportWidth = getWindowTop().innerWidth; + activeViewportHeight = getWindowTop().innerHeight; } catch (e) { - utils.logWarn(`SizeMappingv2:: Unfriendly iframe blocks viewport size to be evaluated correctly`); + logWarn(`SizeMappingv2:: Unfriendly iframe blocks viewport size to be evaluated correctly`); activeViewportWidth = window.innerWidth; activeViewportHeight = window.innerHeight; } @@ -490,7 +489,7 @@ export function getAdUnitDetail(auctionId, adUnit, labels) { const adUnitsForAuction = sizeMappingInternalStore.getAuctionDetail(auctionId).adUnits; // check if the adUnit exists already in the sizeMappingInterStore (check for equivalence of 'code' && 'mediaTypes' properties) - const adUnitDetail = adUnitsForAuction.filter(adUnitDetail => adUnitDetail.adUnitCode === adUnit.code && utils.deepEqual(adUnitDetail.mediaTypes, adUnit.mediaTypes)); + const adUnitDetail = adUnitsForAuction.filter(adUnitDetail => adUnitDetail.adUnitCode === adUnit.code && deepEqual(adUnitDetail.mediaTypes, adUnit.mediaTypes)); if (adUnitDetail.length > 0) { adUnitDetail[0].cacheHits++; @@ -514,7 +513,7 @@ export function getAdUnitDetail(auctionId, adUnit, labels) { // set adUnitDetail in sizeMappingInternalStore against the correct 'auctionId'. sizeMappingInternalStore.setAuctionDetail(auctionId, adUnitDetail); - isLabelActivated && utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Active size buckets after filtration: `, sizeBucketToSizeMap); + isLabelActivated && logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Active size buckets after filtration: `, sizeBucketToSizeMap); return adUnitDetail; } @@ -522,13 +521,13 @@ export function getAdUnitDetail(auctionId, adUnit, labels) { export function getBids({ bidderCode, auctionId, bidderRequestId, adUnits, labels, src }) { return adUnits.reduce((result, adUnit) => { - if (adUnit.mediaTypes && utils.isValidMediaTypes(adUnit.mediaTypes)) { + if (adUnit.mediaTypes && isValidMediaTypes(adUnit.mediaTypes)) { const { activeViewport, transformedMediaTypes, instance: adUnitInstance, isLabelActivated, cacheHits } = internal.getAdUnitDetail(auctionId, adUnit, labels); if (isLabelActivated) { // check if adUnit has any active media types remaining, if not drop the adUnit from auction, // else proceed to evaluate the bids object. if (Object.keys(transformedMediaTypes).length === 0) { - cacheHits === 0 && utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Ad unit disabled since there are no active media types after sizeConfig filtration.`); + cacheHits === 0 && logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Ad unit disabled since there are no active media types after sizeConfig filtration.`); return result; } result @@ -536,19 +535,13 @@ export function getBids({ bidderCode, auctionId, bidderRequestId, adUnits, label .reduce((bids, bid) => { if (internal.isLabelActivated(bid, labels, adUnit.code, adUnitInstance)) { // handle native params - const nativeParams = adUnit.nativeParams || utils.deepAccess(adUnit, 'mediaTypes.native'); - if (nativeParams) { - bid = Object.assign({}, bid, { - nativeParams: processNativeAdUnitParams(nativeParams) - }); - } - bid = Object.assign({}, bid, utils.getDefinedParams(adUnit, ['mediaType', 'renderer'])); + bid = Object.assign({}, bid, getDefinedParams(adUnit, ['mediaType', 'renderer', 'nativeParams'])); if (bid.sizeConfig) { const relevantMediaTypes = internal.getRelevantMediaTypesForBidder(bid.sizeConfig, activeViewport); if (relevantMediaTypes.length === 0) { - utils.logError(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bidderCode} => 'sizeConfig' is not configured properly. This bidder won't be eligible for sizeConfig checks and will remail active.`); + logError(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bidderCode} => 'sizeConfig' is not configured properly. This bidder won't be eligible for sizeConfig checks and will remail active.`); bid = Object.assign({}, bid); } else if (relevantMediaTypes[0] !== 'none') { const bidderMediaTypes = Object @@ -562,20 +555,20 @@ export function getBids({ bidderCode, auctionId, bidderRequestId, adUnits, label if (Object.keys(bidderMediaTypes).length > 0) { bid = Object.assign({}, bid, { mediaTypes: bidderMediaTypes }); } else { - utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' does not match with any of the active mediaTypes at the Ad Unit level. This bidder is disabled.`); + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' does not match with any of the active mediaTypes at the Ad Unit level. This bidder is disabled.`); return bids; } } else { - utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' is set to 'none' in sizeConfig for current viewport size. This bidder is disabled.`); + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' is set to 'none' in sizeConfig for current viewport size. This bidder is disabled.`); return bids; } } bids.push(Object.assign({}, bid, { adUnitCode: adUnit.code, transactionId: adUnit.transactionId, - sizes: utils.deepAccess(transformedMediaTypes, 'banner.sizes') || utils.deepAccess(transformedMediaTypes, 'video.playerSize') || [], + sizes: deepAccess(transformedMediaTypes, 'banner.sizes') || deepAccess(transformedMediaTypes, 'video.playerSize') || [], mediaTypes: bid.mediaTypes || transformedMediaTypes, - bidId: bid.bid_id || utils.getUniqueIdentifierStr(), + bidId: bid.bid_id || getUniqueIdentifierStr(), bidderRequestId, auctionId, src, @@ -585,17 +578,17 @@ export function getBids({ bidderCode, auctionId, bidderRequestId, adUnits, label })); return bids; } else { - utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => Label check for this bidder has failed. This bidder is disabled.`); + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => Label check for this bidder has failed. This bidder is disabled.`); return bids; } }, [])); } else { - cacheHits === 0 && utils.logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Ad unit is disabled due to failing label check.`); + cacheHits === 0 && logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Ad unit is disabled due to failing label check.`); } } else { - utils.logWarn(`Size Mapping V2:: Ad Unit: ${adUnit.code} => Ad unit has declared invalid 'mediaTypes' or has not declared a 'mediaTypes' property`); + logWarn(`Size Mapping V2:: Ad Unit: ${adUnit.code} => Ad unit has declared invalid 'mediaTypes' or has not declared a 'mediaTypes' property`); return result; } return result; - }, []).reduce(utils.flatten, []).filter(val => val !== ''); + }, []).reduce(flatten, []).filter(val => val !== ''); } diff --git a/modules/slimcutBidAdapter.js b/modules/slimcutBidAdapter.js index d717f3a88bd..c2592137fd8 100644 --- a/modules/slimcutBidAdapter.js +++ b/modules/slimcutBidAdapter.js @@ -1,15 +1,16 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { ajax } from '../src/ajax.js'; - +import { getValue, parseSizesInput, getBidIdParameter } from '../src/utils.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + ajax +} from '../src/ajax.js'; const BIDDER_CODE = 'slimcut'; const ENDPOINT_URL = 'https://sb.freeskreen.com/pbr'; - export const spec = { code: BIDDER_CODE, aliases: ['scm'], supportedMediaTypes: ['video', 'banner'], - /** * Determines whether or not the given bid request is valid. * @@ -18,12 +19,11 @@ export const spec = { */ isBidRequestValid: function(bid) { let isValid = false; - if (typeof bid.params !== 'undefined' && !isNaN(parseInt(utils.getValue(bid.params, 'placementId'))) && parseInt(utils.getValue(bid.params, 'placementId')) > 0) { + if (typeof bid.params !== 'undefined' && !isNaN(parseInt(getValue(bid.params, 'placementId'))) && parseInt(getValue(bid.params, 'placementId')) > 0) { isValid = true; } return isValid; }, - /** * Make a server request from the list of BidRequests. * @@ -37,7 +37,6 @@ export const spec = { data: bids, deviceWidth: screen.width }; - let gdpr = bidderRequest.gdprConsent; if (bidderRequest && gdpr) { let isCmp = (typeof gdpr.gdprApplies === 'boolean') @@ -47,7 +46,6 @@ export const spec = { status: isCmp ? gdpr.gdprApplies : -1 }; } - const payloadString = JSON.stringify(payload); return { method: 'POST', @@ -55,7 +53,6 @@ export const spec = { data: payloadString, }; }, - /** * Unpack the response from the server into a list of bids. * @@ -65,9 +62,8 @@ export const spec = { interpretResponse: function(serverResponse, request) { const bidResponses = []; serverResponse = serverResponse.body; - if (serverResponse.responses) { - serverResponse.responses.forEach(function (bid) { + serverResponse.responses.forEach(function(bid) { const bidResponse = { cpm: bid.cpm, width: bid.width, @@ -79,14 +75,16 @@ export const spec = { requestId: bid.requestId, creativeId: bid.creativeId, transactionId: bid.tranactionId, - winUrl: bid.winUrl + winUrl: bid.winUrl, + meta: { + advertiserDomains: bid.adomain || [] + } }; bidResponses.push(bidResponse); }); } return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses) { if (syncOptions.iframeEnabled) { return [{ @@ -96,27 +94,22 @@ export const spec = { } return []; }, - onBidWon: function(bid) { ajax(bid.winUrl + bid.cpm, null); } } - function buildRequestObject(bid) { const reqObj = {}; - let placementId = utils.getValue(bid.params, 'placementId'); - - reqObj.sizes = utils.parseSizesInput(bid.sizes); - reqObj.bidId = utils.getBidIdParameter('bidId', bid); - reqObj.bidderRequestId = utils.getBidIdParameter('bidderRequestId', bid); + let placementId = getValue(bid.params, 'placementId'); + reqObj.sizes = parseSizesInput(bid.sizes); + reqObj.bidId = getBidIdParameter('bidId', bid); + reqObj.bidderRequestId = getBidIdParameter('bidderRequestId', bid); reqObj.placementId = parseInt(placementId); - reqObj.adUnitCode = utils.getBidIdParameter('adUnitCode', bid); - reqObj.auctionId = utils.getBidIdParameter('auctionId', bid); - reqObj.transactionId = utils.getBidIdParameter('transactionId', bid); - + reqObj.adUnitCode = getBidIdParameter('adUnitCode', bid); + reqObj.auctionId = getBidIdParameter('auctionId', bid); + reqObj.transactionId = getBidIdParameter('transactionId', bid); return reqObj; } - function getReferrerInfo(bidderRequest) { let ref = window.location.href; if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 7552bbbf162..1764ac11117 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -1,11 +1,13 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { deepAccess, getDNT, deepSetValue, logInfo, logError, isEmpty, getAdUnitSizes, fill, chunk, getMaxValueFromArray, getMinValueFromArray } from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const CLIENT = 'prebid_js_$prebid.version$_1.1' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.6'; +const CURRENCY = 'USD'; +const CLIENT = 'prebid_js_$prebid.version$_1.1'; const GVLID = 82; /** @@ -43,38 +45,16 @@ const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { } } - if (videoMediaType) { - result.video = { - mimes: videoMediaType.mimes, - minduration: videoMediaType.minduration, - startdelay: videoMediaType.startdelay, - linearity: videoMediaType.linearity, - w: videoMediaType.playerSize[0][0], - h: videoMediaType.playerSize[0][1], - maxduration: videoMediaType.maxduration, - skip: videoMediaType.skip, - protocols: videoMediaType.protocols, - ext: { - rewarded: videoMediaType.ext && videoMediaType.ext.rewarded ? videoMediaType.ext.rewarded : 0 - }, - skipmin: videoMediaType.skipmin, - api: videoMediaType.api - } - } - - return result; - }); - - const request = { +const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { + const requestTemplate = { id: bidderRequest.auctionId, at: 1, - imp, - cur: ['USD'], + cur: [CURRENCY], tmax: bidderRequest.timeout, site: { id: window.location.hostname, publisher: { - id: utils.deepAccess(validBidRequests[0], 'params.publisherId') + id: deepAccess(bidRequest, 'params.publisherId') }, domain: window.location.hostname, page: window.location.href, @@ -83,7 +63,7 @@ const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { device: { language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', ua: navigator.userAgent, - dnt: utils.getDNT() ? 1 : 0, + dnt: getDNT() ? 1 : 0, h: screen.height, w: screen.width }, @@ -94,39 +74,74 @@ const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { user: { ext: {} }, + source: { + ext: { + schain: bidRequest.schain + } + }, ext: { - client: CLIENT + client: SMAATO_CLIENT } }; let ortb2 = config.getConfig('ortb2') || {}; - - Object.assign(request.user, ortb2.user); - Object.assign(request.site, ortb2.site); + Object.assign(requestTemplate.user, ortb2.user); + Object.assign(requestTemplate.site, ortb2.site); if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { - utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); - utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(requestTemplate, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + deepSetValue(requestTemplate, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } if (bidderRequest.uspConsent !== undefined) { - utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(requestTemplate, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - if (utils.deepAccess(validBidRequests[0], 'params.app')) { - const geo = utils.deepAccess(validBidRequests[0], 'params.app.geo'); - utils.deepSetValue(request, 'device.geo', geo); - const ifa = utils.deepAccess(validBidRequests[0], 'params.app.ifa') - utils.deepSetValue(request, 'device.ifa', ifa); + if (deepAccess(bidRequest, 'params.app')) { + const geo = deepAccess(bidRequest, 'params.app.geo'); + deepSetValue(requestTemplate, 'device.geo', geo); + const ifa = deepAccess(bidRequest, 'params.app.ifa') + deepSetValue(requestTemplate, 'device.ifa', ifa); } - const eids = utils.deepAccess(validBidRequests[0], 'userIdAsEids'); + const eids = deepAccess(bidRequest, 'userIdAsEids'); if (eids && eids.length) { - utils.deepSetValue(request, 'user.ext.eids', eids); + deepSetValue(requestTemplate, 'user.ext.eids', eids); + } + + let requests = []; + + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + const bannerRequest = Object.assign({}, requestTemplate, createBannerImp(bidRequest)); + requests.push(bannerRequest); + } + + const videoMediaType = deepAccess(bidRequest, 'mediaTypes.video'); + if (videoMediaType) { + if (videoMediaType.context === ADPOD) { + const adPodRequest = Object.assign({}, requestTemplate, createAdPodImp(bidRequest, videoMediaType)); + addOptionalAdpodParameters(adPodRequest, videoMediaType); + requests.push(adPodRequest); + } else { + const videoRequest = Object.assign({}, requestTemplate, createVideoImp(bidRequest, videoMediaType)); + requests.push(videoRequest); + } } - utils.logInfo('[SMAATO] OpenRTB Request:', request); - return JSON.stringify(request); + return requests; +} + +const buildServerRequest = (validBidRequest, data) => { + logInfo('[SMAATO] OpenRTB Request:', data); + return { + method: 'POST', + url: validBidRequest.params.endpoint || SMAATO_ENDPOINT, + data: JSON.stringify(data), + options: { + withCredentials: true, + crossOrigin: true, + } + }; } export const spec = { @@ -135,117 +150,153 @@ export const spec = { gvlid: GVLID, /** - * 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. - */ + * 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: (bid) => { - return typeof bid.params === 'object' && - typeof bid.params.publisherId === 'string' && - typeof bid.params.adspaceId === 'string'; + if (typeof bid.params !== 'object') { + logError('[SMAATO] Missing params object'); + return false; + } + + if (typeof bid.params.publisherId !== 'string') { + logError('[SMAATO] Missing mandatory publisherId param'); + return false; + } + + if (deepAccess(bid, 'mediaTypes.video.context') === ADPOD) { + logInfo('[SMAATO] Verifying adpod bid request'); + + if (typeof bid.params.adbreakId !== 'string') { + logError('[SMAATO] Missing for adpod request mandatory adbreakId param'); + return false; + } + + if (bid.params.adspaceId) { + logError('[SMAATO] The adspaceId param is not allowed in an adpod bid request'); + return false; + } + } else { + logInfo('[SMAATO] Verifying a non adpod bid request'); + + if (typeof bid.params.adspaceId !== 'string') { + logError('[SMAATO] Missing mandatory adspaceId param'); + return false; + } + + if (bid.params.adbreakId) { + logError('[SMAATO] The adbreakId param is only allowed in an adpod bid request'); + return false; + } + } + + logInfo('[SMAATO] Verification done, all good'); + return true; }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ buildRequests: (validBidRequests, bidderRequest) => { - utils.logInfo('[SMAATO] Client version:', CLIENT); - return { - method: 'POST', - url: validBidRequests[0].params.endpoint || SMAATO_ENDPOINT, - data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest), - options: { - withCredentials: true, - crossOrigin: true, - } - }; + logInfo('[SMAATO] Client version:', SMAATO_CLIENT); + + return validBidRequests.map((validBidRequest) => { + const openRtbBidRequests = buildOpenRtbBidRequest(validBidRequest, bidderRequest); + return openRtbBidRequests.map((openRtbBidRequest) => buildServerRequest(validBidRequest, openRtbBidRequest)); + }).reduce((acc, item) => item != null && acc.concat(item), []); }, /** - * 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: (serverResponse, bidRequest) => { // response is empty (HTTP 204) - if (utils.isEmpty(serverResponse.body)) { - utils.logInfo('[SMAATO] Empty response body HTTP 204, no bids'); + if (isEmpty(serverResponse.body)) { + logInfo('[SMAATO] Empty response body HTTP 204, no bids'); return []; // no bids } - let serverResponseHeaders = serverResponse.headers; - const smtAdType = serverResponseHeaders.get('X-SMT-ADTYPE'); + const serverResponseHeaders = serverResponse.headers; const smtExpires = serverResponseHeaders.get('X-SMT-Expires'); - let ttlSec = 300; - utils.logInfo('[SMAATO] Expires:', smtExpires); - if (smtExpires) { - ttlSec = Math.floor((smtExpires - Date.now()) / 1000); - } + logInfo('[SMAATO] Expires:', smtExpires); + const ttlInSec = smtExpires ? Math.floor((smtExpires - Date.now()) / 1000) : 300; - const res = serverResponse.body; - utils.logInfo('[SMAATO] OpenRTB Response:', res); + const response = serverResponse.body; + logInfo('[SMAATO] OpenRTB Response:', response); - var bids = []; - res.seatbid.forEach(sb => { - sb.bid.forEach(b => { + const smtAdType = serverResponseHeaders.get('X-SMT-ADTYPE'); + const bids = []; + response.seatbid.forEach(seatbid => { + seatbid.bid.forEach(bid => { let resultingBid = { - requestId: b.impid, - cpm: b.price || 0, - width: b.w, - height: b.h, - ttl: ttlSec, - creativeId: b.crid, - dealId: b.dealid || null, - netRevenue: utils.deepAccess(b, 'ext.net', true), - currency: res.cur, + requestId: bid.impid, + cpm: bid.price || 0, + width: bid.w, + height: bid.h, + ttl: ttlInSec, + creativeId: bid.crid, + dealId: bid.dealid || null, + netRevenue: deepAccess(bid, 'ext.net', true), + currency: response.cur, meta: { - advertiserDomains: b.adomain, - networkName: b.bidderName, - agencyId: sb.seat + advertiserDomains: bid.adomain, + networkName: bid.bidderName, + agencyId: seatbid.seat } }; - switch (smtAdType) { - case 'Img': - resultingBid.ad = createImgAd(b.adm); - resultingBid.meta.mediaType = BANNER; - bids.push(resultingBid); - break; - case 'Richmedia': - resultingBid.ad = createRichmediaAd(b.adm); - resultingBid.meta.mediaType = BANNER; - bids.push(resultingBid); - break; - case 'Video': - resultingBid.vastXml = b.adm; - resultingBid.meta.mediaType = VIDEO; - bids.push(resultingBid); - break; - default: - utils.logInfo('[SMAATO] Invalid ad type:', smtAdType); + const videoContext = deepAccess(JSON.parse(bidRequest.data).imp[0], 'video.ext.context') + if (videoContext === ADPOD) { + resultingBid.vastXml = bid.adm; + resultingBid.mediaType = VIDEO; + if (config.getConfig('adpod.brandCategoryExclusion')) { + resultingBid.meta.primaryCatId = bid.cat[0]; + } + resultingBid.video = { + context: ADPOD, + durationSeconds: bid.ext.duration + }; + bids.push(resultingBid); + } else { + switch (smtAdType) { + case 'Img': + resultingBid.ad = createImgAd(bid.adm); + resultingBid.mediaType = BANNER; + bids.push(resultingBid); + break; + case 'Richmedia': + resultingBid.ad = createRichmediaAd(bid.adm); + resultingBid.mediaType = BANNER; + bids.push(resultingBid); + break; + case 'Video': + resultingBid.vastXml = bid.adm; + resultingBid.mediaType = VIDEO; + bids.push(resultingBid); + break; + default: + logInfo('[SMAATO] Invalid ad type:', smtAdType); + } } + resultingBid.meta.mediaType = resultingBid.mediaType; }); }); - utils.logInfo('[SMAATO] Prebid bids:', bids); + logInfo('[SMAATO] Prebid bids:', bids); return bids; }, /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - const syncs = [] - return syncs; + return []; } } registerBidder(spec); @@ -282,3 +333,153 @@ const createRichmediaAd = (adm) => { return markup + ''; }; + +function createBannerImp(bidRequest) { + const adUnitSizes = getAdUnitSizes(bidRequest); + const sizes = adUnitSizes.map((size) => ({w: size[0], h: size[1]})); + return { + imp: [{ + id: bidRequest.bidId, + tagid: deepAccess(bidRequest, 'params.adspaceId'), + bidfloor: getBidFloor(bidRequest, BANNER, adUnitSizes), + instl: deepAccess(bidRequest.ortb2Imp, 'instl'), + banner: { + w: sizes[0].w, + h: sizes[0].h, + format: sizes + } + }] + }; +} + +function createVideoImp(bidRequest, videoMediaType) { + return { + imp: [{ + id: bidRequest.bidId, + tagid: deepAccess(bidRequest, 'params.adspaceId'), + bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), + instl: deepAccess(bidRequest.ortb2Imp, 'instl'), + video: { + mimes: videoMediaType.mimes, + minduration: videoMediaType.minduration, + startdelay: videoMediaType.startdelay, + linearity: videoMediaType.linearity, + w: videoMediaType.playerSize[0][0], + h: videoMediaType.playerSize[0][1], + maxduration: videoMediaType.maxduration, + skip: videoMediaType.skip, + protocols: videoMediaType.protocols, + ext: { + rewarded: videoMediaType.ext && videoMediaType.ext.rewarded ? videoMediaType.ext.rewarded : 0 + }, + skipmin: videoMediaType.skipmin, + api: videoMediaType.api + } + }] + }; +} + +function createAdPodImp(bidRequest, videoMediaType) { + const tagid = deepAccess(bidRequest, 'params.adbreakId') + const bce = config.getConfig('adpod.brandCategoryExclusion') + let imp = { + id: bidRequest.bidId, + tagid: tagid, + bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), + instl: deepAccess(bidRequest.ortb2Imp, 'instl'), + video: { + w: videoMediaType.playerSize[0][0], + h: videoMediaType.playerSize[0][1], + mimes: videoMediaType.mimes, + startdelay: videoMediaType.startdelay, + linearity: videoMediaType.linearity, + skip: videoMediaType.skip, + protocols: videoMediaType.protocols, + skipmin: videoMediaType.skipmin, + api: videoMediaType.api, + ext: { + context: ADPOD, + brandcategoryexclusion: bce !== undefined && bce + } + } + } + + const numberOfPlacements = getAdPodNumberOfPlacements(videoMediaType) + let imps = fill(imp, numberOfPlacements) + + const durationRangeSec = videoMediaType.durationRangeSec + if (videoMediaType.requireExactDuration) { + // equal distribution of numberOfPlacement over all available durations + const divider = Math.ceil(numberOfPlacements / durationRangeSec.length) + const chunked = chunk(imps, divider) + + // each configured duration is set as min/maxduration for a subset of requests + durationRangeSec.forEach((duration, index) => { + chunked[index].map(imp => { + const sequence = index + 1; + imp.video.minduration = duration + imp.video.maxduration = duration + imp.video.sequence = sequence + }); + }); + } else { + // all maxdurations should be the same + const maxDuration = getMaxValueFromArray(durationRangeSec); + imps.map((imp, index) => { + const sequence = index + 1; + imp.video.maxduration = maxDuration + imp.video.sequence = sequence + }); + } + + return { + imp: imps + } +} + +function getAdPodNumberOfPlacements(videoMediaType) { + const {adPodDurationSec, durationRangeSec, requireExactDuration} = videoMediaType + const minAllowedDuration = getMinValueFromArray(durationRangeSec) + const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration) + + return requireExactDuration + ? Math.max(numberOfPlacements, durationRangeSec.length) + : numberOfPlacements +} + +const addOptionalAdpodParameters = (request, videoMediaType) => { + const content = {} + + if (videoMediaType.tvSeriesName) { + content.series = videoMediaType.tvSeriesName + } + if (videoMediaType.tvEpisodeName) { + content.title = videoMediaType.tvEpisodeName + } + if (typeof videoMediaType.tvSeasonNumber === 'number') { + content.season = videoMediaType.tvSeasonNumber.toString() // conversion to string as in OpenRTB season is a string + } + if (typeof videoMediaType.tvEpisodeNumber === 'number') { + content.episode = videoMediaType.tvEpisodeNumber + } + if (typeof videoMediaType.contentLengthSec === 'number') { + content.len = videoMediaType.contentLengthSec + } + if (videoMediaType.contentMode && ['live', 'on-demand'].indexOf(videoMediaType.contentMode) >= 0) { + content.livestream = videoMediaType.contentMode === 'live' ? 1 : 0 + } + + if (!isEmpty(content)) { + request.site.content = content + } +} + +function getBidFloor(bidRequest, mediaType, sizes) { + if (typeof bidRequest.getFloor === 'function') { + const size = sizes.length === 1 ? sizes[0] : '*'; + const floor = bidRequest.getFloor({currency: CURRENCY, mediaType: mediaType, size: size}); + if (floor && !isNaN(floor.floor) && (floor.currency === CURRENCY)) { + return floor.floor; + } + } +} diff --git a/modules/smaatoBidAdapter.md b/modules/smaatoBidAdapter.md index d26d7ecf64e..41e1c952f2a 100644 --- a/modules/smaatoBidAdapter.md +++ b/modules/smaatoBidAdapter.md @@ -61,4 +61,35 @@ var adUnits = [{ } }] }]; -``` \ No newline at end of file +``` + +For adpod adunits: + +``` +var adUnits = [{ + "code": "adpod unit", + "mediaTypes": { + "video": { + "context": "adpod", + "playerSize": [640, 480], + "adPodDurationSec": 300, + "durationRangeSec": [15, 30], + "requireExactDuration": false, + "mimes": ["video/mp4"], + "startdelay": 0, + "linearity": 1, + "protocols": [7], + "skip": 1, + "skipmin": 5, + "api": [7], + } + }, + "bids": [{ + "bidder": "smaato", + "params": { + "publisherId": "1100042525", + "adbreakId": "330563103" + } + }] +}]; +``` diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index bb9364c72c3..d6e1c8de452 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -1,19 +1,13 @@ -import * as utils from '../src/utils.js'; -import { - BANNER, - VIDEO -} from '../src/mediaTypes.js'; -import { - config -} from '../src/config.js'; -import { - registerBidder -} from '../src/adapters/bidderFactory.js'; -import { - createEidsArray -} from './userId/eids.js'; +import { deepAccess, deepClone, logError, isFn, isPlainObject } from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { createEidsArray } from './userId/eids.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + const BIDDER_CODE = 'smartadserver'; const GVL_ID = 45; +const DEFAULT_FLOOR = 0.0; + export const spec = { code: BIDDER_CODE, gvlid: GVL_ID, @@ -45,7 +39,91 @@ export const spec = { }, /** - * Make a server request from the list of BidRequests. + * Transforms the banner ad unit sizes into an object array. + * + * @param {*} bannerSizes Array of size array (ex. [[300, 250]]). + * @returns + */ + adaptBannerSizes: function(bannerSizes) { + return bannerSizes.map(size => ({ + w: size[0], + h: size[1] + })); + }, + + /** + * Fills the payload with specific video attributes. + * + * @param {*} payload Payload that will be sent in the ServerRequest + * @param {*} videoMediaType Video media type. + */ + fillPayloadForVideoBidRequest: function(payload, videoMediaType, videoParams) { + const playerSize = videoMediaType.playerSize[0]; + payload.isVideo = videoMediaType.context === 'instream'; + payload.mediaType = VIDEO; + payload.videoData = { + videoProtocol: this.getProtocolForVideoBidRequest(videoMediaType, videoParams), + playerWidth: playerSize[0], + playerHeight: playerSize[1], + adBreak: this.getStartDelayForVideoBidRequest(videoMediaType, videoParams) + }; + }, + + /** + * Gets the protocols from either videoParams or VideoMediaType + * @param {*} videoMediaType + * @param {*} videoParams + * @returns protocol from either videoMediaType or videoParams + */ + getProtocolForVideoBidRequest: function(videoMediaType, videoParams) { + if (videoParams !== undefined && videoParams.protocol) { + return videoParams.protocol; + } else if (videoMediaType !== undefined) { + if (Array.isArray(videoMediaType.protocols)) { + return Math.max.apply(Math, videoMediaType.protocols); + } + } + return null; + }, + + /** + * Gets the startDelay from either videoParams or VideoMediaType + * @param {*} videoMediaType + * @param {*} videoParams + * @returns positive integer value of startdelay + */ + getStartDelayForVideoBidRequest: function(videoMediaType, videoParams) { + if (videoParams !== undefined && videoParams.startDelay) { + return videoParams.startDelay; + } else if (videoMediaType !== undefined) { + if (videoMediaType.startdelay == 0) { + return 1; + } else if (videoMediaType.startdelay == -1) { + return 2; + } else if (videoMediaType.startdelay == -2) { + return 3; + } + } + return 2;// Default value for all exotic cases set to bid.params.video.startDelay midroll hence 2. + }, + + /** + * Creates the server request. + * + * @param {*} payload Body of the request. + * @param {string} domain Endpoint domain . + * @returns {ServerRequest} Info describing the request to the server. + */ + createServerRequest: function(payload, domain) { + return { + method: 'POST', + url: (domain !== undefined ? domain : 'https://prg.smartadserver.com') + '/prebid/v1', + data: JSON.stringify(payload), + }; + }, + + /** + * Makes server requests from the list of BidRequests. * * @param {BidRequest[]} validBidRequests an array of bids * @param {BidderRequest} bidderRequest bidder request object @@ -53,18 +131,18 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { // use bidderRequest.bids[] to get bidder-dependent request info - // if your bidder supports multiple currencies, use config.getConfig(currency) - // to find which one the ad server needs + + const adServerCurrency = config.getConfig('currency.adServerCurrency'); // pull requested transaction ID from bidderRequest.bids[].transactionId - return validBidRequests.map(bid => { + return validBidRequests.reduce((bidRequests, bid) => { // Common bid request attributes for banner, outstream and instream. let payload = { siteid: bid.params.siteId, pageid: bid.params.pageId, formatid: bid.params.formatId, - currencyCode: config.getConfig('currency.adServerCurrency'), - bidfloor: bid.params.bidfloor || 0.0, + currencyCode: adServerCurrency, + bidfloor: bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency), targeting: bid.params.target && bid.params.target !== '' ? bid.params.target : undefined, buid: bid.params.buId && bid.params.buId !== '' ? bid.params.buId : undefined, appname: bid.params.appName && bid.params.appName !== '' ? bid.params.appName : undefined, @@ -78,28 +156,6 @@ export const spec = { schain: spec.serializeSupplyChain(bid.schain) }; - const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); - if (!videoMediaType) { - const bannerMediaType = utils.deepAccess(bid, 'mediaTypes.banner'); - payload.sizes = bannerMediaType.sizes.map(size => ({ - w: size[0], - h: size[1] - })); - } else if (videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream')) { - // Specific attributes for instream. - let playerSize = videoMediaType.playerSize[0]; - payload.isVideo = videoMediaType.context === 'instream'; - payload.mediaType = VIDEO; - payload.videoData = { - videoProtocol: bid.params.video.protocol, - playerWidth: playerSize[0], - playerHeight: playerSize[1], - adBreak: bid.params.video.startDelay || 1 - }; - } else { - return {}; - } - if (bidderRequest && bidderRequest.gdprConsent) { payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; payload.gdpr_consent = bidderRequest.gdprConsent.consentString; @@ -114,14 +170,31 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } - var payloadString = JSON.stringify(payload); + const videoMediaType = deepAccess(bid, 'mediaTypes.video'); + const bannerMediaType = deepAccess(bid, 'mediaTypes.banner'); + const isAdUnitContainingVideo = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); + if (!isAdUnitContainingVideo && bannerMediaType) { + payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); + bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); + } else if (isAdUnitContainingVideo && !bannerMediaType) { + spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); + bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); + } else if (isAdUnitContainingVideo && bannerMediaType) { + // If there are video and banner media types in the ad unit, we clone the payload + // to create a specific one for video. + let videoPayload = deepClone(payload); - return { - method: 'POST', - url: (bid.params.domain !== undefined ? bid.params.domain : 'https://prg.smartadserver.com') + '/prebid/v1', - data: payloadString, - }; - }); + spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); + bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); + + payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); + bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); + } else { + bidRequests.push({}); + } + + return bidRequests; + }, []); }, /** @@ -148,7 +221,8 @@ export const spec = { currency: response.currency, netRevenue: response.isNetCpm, ttl: response.ttl, - dspPixels: response.dspPixels + dspPixels: response.dspPixels, + meta: { advertiserDomains: response.adomain ? response.adomain : [] } }; if (bidRequest.mediaType === VIDEO) { @@ -164,11 +238,36 @@ export const spec = { bidResponses.push(bidResponse); } } catch (error) { - utils.logError('Error while parsing smart server response', error); + logError('Error while parsing smart server response', error); } return bidResponses; }, + /** + * Get floors from Prebid Price Floors module + * + * @param {object} bid Bid request object + * @param {string} currency Ad server currency + * @return {number} Floor price + */ + getBidFloor: function (bid, currency) { + if (!isFn(bid.getFloor)) { + return DEFAULT_FLOOR; + } + + const floor = bid.getFloor({ + currency: currency || 'USD', + mediaType: '*', + size: '*' + }); + + if (isPlainObject(floor) && !isNaN(floor.floor)) { + return floor.floor; + } + + return DEFAULT_FLOOR; + }, + /** * User syncs. * @@ -178,7 +277,7 @@ export const spec = { */ getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; - if (syncOptions.iframeEnabled && serverResponses.length > 0) { + if (syncOptions.iframeEnabled && serverResponses.length > 0 && serverResponses[0].body.cSyncUrl != null) { syncs.push({ type: 'iframe', url: serverResponses[0].body.cSyncUrl diff --git a/modules/smartadserverBidAdapter.md b/modules/smartadserverBidAdapter.md index 05e29359fd2..7a2381ac9f1 100644 --- a/modules/smartadserverBidAdapter.md +++ b/modules/smartadserverBidAdapter.md @@ -110,11 +110,11 @@ Please reach out to your Technical account manager for more information. renderer: { url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', render: function (bid) { - bid.renderer.push(() => { - ANOutstreamVideo.renderAd({ + bid.renderer.push(() => { + ANOutstreamVideo.renderAd({ targetId: bid.adUnitCode, - adResponse: bid - }); + adResponse: bid + }); }); } }, @@ -133,4 +133,34 @@ Please reach out to your Technical account manager for more information. } }] }; +``` + +## Double Mediatype Setup (Banner & Video) + +``` + var adUnits = [{ + code: 'prebid_tag_001', + mediaTypes: { + banner: { + sizes: [[300,250]] + }, + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + bids: [{ + bidder: 'smartadserver', + params: { + domain: 'https://prg.smartadserver.com', + siteId: 411951, + pageId: 1383641, + formatId: 84313, + target: 'iid=8984466', + video: { + protocol: 6, // Stands for "up to VAST 3". For "up to VAST 4" it is 8 + } + } + }] + }]; ``` \ No newline at end of file diff --git a/modules/smarticoBidAdapter.js b/modules/smarticoBidAdapter.js index 9107ce5f908..2399a12f932 100644 --- a/modules/smarticoBidAdapter.js +++ b/modules/smarticoBidAdapter.js @@ -66,9 +66,8 @@ export const spec = { method: SMARTICO_CONFIG.method, url: SMARTICO_CONFIG.bidRequestUrl, bids: validBidRequests, - data: {bidParams: bidParams, auctionId: bidderRequest.auctionId, origin: window.location.origin} + data: {bidParams: bidParams, auctionId: bidderRequest.auctionId} } - return ServerRequestObjects; }, interpretResponse: function (serverResponse, bidRequest) { @@ -78,13 +77,14 @@ export const spec = { var url var html var ad + var ads var token var language var scriptId var bidResponses = [] - - for (i = 0; i < serverResponse.length; i++) { - ad = serverResponse[i]; + ads = serverResponse.body + for (i = 0; i < ads.length; i++) { + ad = ads[i] bid = find(bidRequest.bids, bid => bid.bidId === ad.bidId) if (bid) { token = bid.params.token || '' @@ -103,9 +103,14 @@ export const spec = { width: parseInt(ad.bannerFormatWidth), height: parseInt(ad.bannerFormatHeight), creativeId: ad.id, - netRevenue: false, // gross + netRevenue: !!ad.netRevenue, + currency: ad.currency, ttl: ad.ttl, - ad: html + ad: html, + meta: { + advertiserDomains: ad.domains, + advertiserName: ad.title + } } bidResponses.push(bidObject); } diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index b2c48b58f82..00c962445d9 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { logError, deepAccess, isArray, getBidIdParameter, getDNT, generateUUID, isEmpty, _each, logMessage, logWarn, isFn, isPlainObject } from '../src/utils.js'; import { Renderer } from '../src/Renderer.js'; @@ -22,51 +22,39 @@ export const spec = { */ isBidRequestValid: function (bid) { if (bid && typeof bid.params !== 'object') { - utils.logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); + logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); return false; } - if (!utils.deepAccess(bid, 'mediaTypes.video')) { - utils.logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); + if (!deepAccess(bid, 'mediaTypes.video')) { + logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); return false; } - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - if (!playerSize || !utils.isArray(playerSize)) { - utils.logError(BIDDER_CODE + ': mediaTypes.video.playerSize is not defined in the bidder settings.'); + const playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); + if (!playerSize || !isArray(playerSize)) { + logError(BIDDER_CODE + ': mediaTypes.video.playerSize is not defined in the bidder settings.'); return false; } - if (!utils.getBidIdParameter('tagId', bid.params)) { - utils.logError(BIDDER_CODE + ': tagId is not present in bidder params'); + if (!getBidIdParameter('tagId', bid.params)) { + logError(BIDDER_CODE + ': tagId is not present in bidder params'); return false; } - if (!utils.getBidIdParameter('publisherId', bid.params)) { - utils.logError(BIDDER_CODE + ': publisherId is not present in bidder params'); + if (!getBidIdParameter('publisherId', bid.params)) { + logError(BIDDER_CODE + ': publisherId is not present in bidder params'); return false; } - if (!utils.getBidIdParameter('siteId', bid.params)) { - utils.logError(BIDDER_CODE + ': siteId is not present in bidder params'); + if (!getBidIdParameter('siteId', bid.params)) { + logError(BIDDER_CODE + ': siteId is not present in bidder params'); return false; } - if (!utils.getBidIdParameter('bidfloor', bid.params)) { - utils.logError(BIDDER_CODE + ': bidfloor is not present in bidder params'); - return false; - } - if (!utils.getBidIdParameter('bidfloorcur', bid.params)) { - utils.logError(BIDDER_CODE + ': bidfloorcur is not present in bidder params'); - return false; - } - if (utils.deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { - if (!utils.getBidIdParameter('outstream_options', bid.params)) { - utils.logError(BIDDER_CODE + ': outstream_options parameter is not defined'); + if (deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { + if (!getBidIdParameter('outstream_options', bid.params)) { + logError(BIDDER_CODE + ': outstream_options parameter is not defined'); return false; } - if (!utils.getBidIdParameter('slot', bid.params.outstream_options)) { - utils.logError(BIDDER_CODE + ': slot parameter is not defined in outstream_options object in the configuration'); + if (!getBidIdParameter('slot', bid.params.outstream_options)) { + logError(BIDDER_CODE + ': slot parameter is not defined in outstream_options object in the configuration'); return false; } - if (!utils.getBidIdParameter('outstream_function', bid.params)) { - utils.logMessage(BIDDER_CODE + ': outstream_function parameter is not defined. The default outstream renderer will be injected in the header.'); - return true; - } } return true; @@ -83,40 +71,40 @@ export const spec = { const isPageSecure = !!page.match(/^https:/) const smartxRequests = bidRequests.map(function (bid) { - const tagId = utils.getBidIdParameter('tagId', bid.params); - const publisherId = utils.getBidIdParameter('publisherId', bid.params); - const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); - const bidfloorcur = utils.getBidIdParameter('bidfloorcur', bid.params); - const siteId = utils.getBidIdParameter('siteId', bid.params); - const domain = utils.getBidIdParameter('domain', bid.params); - const cat = utils.getBidIdParameter('cat', bid.params); + const tagId = getBidIdParameter('tagId', bid.params); + const publisherId = getBidIdParameter('publisherId', bid.params); + const bidfloor = getBidFloor(bid) || 0; + const bidfloorcur = getBidIdParameter('bidfloorcur', bid.params) || 'EUR'; + const siteId = getBidIdParameter('siteId', bid.params); + const domain = getBidIdParameter('domain', bid.params); + const cat = getBidIdParameter('cat', bid.params) || ['']; let pubcid = null; - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); const contentWidth = playerSize[0][0]; const contentHeight = playerSize[0][1]; - const secure = +(isPageSecure || (utils.getBidIdParameter('secure', bid.params) ? 1 : 0)); + const secure = +(isPageSecure || (getBidIdParameter('secure', bid.params) ? 1 : 0)); const ext = { sdk_name: 'Prebid 1+' }; - const mimes = utils.getBidIdParameter('mimes', bid.params) || ['application/javascript', 'video/mp4', 'video/webm']; - const linearity = utils.getBidIdParameter('linearity', bid.params) || 1; - const minduration = utils.getBidIdParameter('minduration', bid.params) || 0; - const maxduration = utils.getBidIdParameter('maxduration', bid.params) || 500; - const startdelay = utils.getBidIdParameter('startdelay', bid.params) || 0; - const minbitrate = utils.getBidIdParameter('minbitrate', bid.params) || 0; - const maxbitrate = utils.getBidIdParameter('maxbitrate', bid.params) || 3500; - const delivery = utils.getBidIdParameter('delivery', bid.params) || [2]; - const pos = utils.getBidIdParameter('pos', bid.params) || 1; - const api = utils.getBidIdParameter('api', bid.params) || [2]; - const protocols = utils.getBidIdParameter('protocols', bid.params) || [2, 3, 5, 6]; - var contextcustom = utils.deepAccess(bid, 'mediaTypes.video.context'); + const mimes = getBidIdParameter('mimes', bid.params) || ['application/javascript', 'video/mp4', 'video/webm']; + const linearity = getBidIdParameter('linearity', bid.params) || 1; + const minduration = getBidIdParameter('minduration', bid.params) || 0; + const maxduration = getBidIdParameter('maxduration', bid.params) || 500; + const startdelay = getBidIdParameter('startdelay', bid.params) || 0; + const minbitrate = getBidIdParameter('minbitrate', bid.params) || 0; + const maxbitrate = getBidIdParameter('maxbitrate', bid.params) || 3500; + const delivery = getBidIdParameter('delivery', bid.params) || [2]; + const pos = getBidIdParameter('pos', bid.params) || 1; + const api = getBidIdParameter('api', bid.params) || [2]; + const protocols = getBidIdParameter('protocols', bid.params) || [2, 3, 5, 6]; + var contextcustom = deepAccess(bid, 'mediaTypes.video.context'); var placement = 1; if (contextcustom === 'outstream') { placement = 3; } - let smartxReq = { + let smartxReq = [{ id: bid.bidId, secure: secure, bidfloor: bidfloor, @@ -142,7 +130,7 @@ export const spec = { ext: { 'smart.bidpricetype': 1 } - }; + }]; if (bid.crumbs && bid.crumbs.pubcid) { pubcid = bid.crumbs.pubcid; @@ -153,32 +141,40 @@ export const spec = { const device = { h: screen.height, w: screen.width, - dnt: utils.getDNT() ? 1 : 0, + dnt: getDNT() ? 1 : 0, language: navigator[language].split('-')[0], make: navigator.vendor ? navigator.vendor : '', ua: navigator.userAgent }; - const at = utils.getBidIdParameter('at', bid.params) || 2; + const at = getBidIdParameter('at', bid.params) || 2; - const cur = utils.getBidIdParameter('cur', bid.params) || ['EUR']; + const cur = getBidIdParameter('cur', bid.params) || ['EUR']; const requestPayload = { - id: utils.generateUUID(), + id: generateUUID(), imp: smartxReq, site: { id: siteId, page: page, cat: cat, - content: 'content', domain: domain, publisher: { id: publisherId + }, + content: { + ext: { + prebid: { + name: 'pbjs', + version: '$prebid.version$' + } + } } }, device: device, at: at, - cur: cur + cur: cur, + ext: {} }; const userExt = {}; @@ -201,14 +197,16 @@ export const spec = { } // Only add the user object if it's not empty - if (!utils.isEmpty(userExt)) { + if (!isEmpty(userExt)) { requestPayload.user = { ext: userExt }; } + // requestPayload.user.ext.ver = pbjs.version; + // Targeting - if (utils.getBidIdParameter('data', bid.params.user)) { + if (getBidIdParameter('data', bid.params.user)) { var targetingarr = []; for (var i = 0; i < bid.params.user.data.length; i++) { var isemq = (bid.params.user.data[i].name) || 'empty'; @@ -226,6 +224,8 @@ export const spec = { } } + // Todo: USER ID MODULE + requestPayload.user = { ext: userExt, data: targetingarr @@ -257,9 +257,9 @@ export const spec = { interpretResponse: function (serverResponse, bidderRequest) { const bidResponses = []; const serverResponseBody = serverResponse.body; - if (serverResponseBody && utils.isArray(serverResponseBody.seatbid)) { - utils._each(serverResponseBody.seatbid, function (bids) { - utils._each(bids.bid, function (smartxBid) { + if (serverResponseBody && isArray(serverResponseBody.seatbid)) { + _each(serverResponseBody.seatbid, function (bids) { + _each(bids.bid, function (smartxBid) { let currentBidRequest = {}; for (let i in bidderRequest.bidRequest.bids) { if (smartxBid.impid == bidderRequest.bidRequest.bids[i].bidId) { @@ -270,7 +270,7 @@ export const spec = { * Make sure currency and price are the right ones * TODO: what about the pre_market_bid partners sizes? */ - utils._each(currentBidRequest.params.pre_market_bids, function (pmb) { + _each(currentBidRequest.params.pre_market_bids, function (pmb) { if (pmb.deal_id == smartxBid.id) { smartxBid.price = pmb.price; serverResponseBody.cur = pmb.currency; @@ -296,39 +296,38 @@ export const spec = { bid.meta.advertiserDomains = smartxBid.adomain; } - const context = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context'); + const context = deepAccess(currentBidRequest, 'mediaTypes.video.context'); if (context === 'outstream') { - const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); + const playersize = deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, - url: '/', + url: 'https://dco.smartclip.net/?plc=7777778', config: { adText: 'SmartX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], player_height: playersize[0][1], - content_page_url: utils.deepAccess(bidderRequest, 'data.site.page'), - ad_mute: +!!utils.deepAccess(currentBidRequest, 'params.ad_mute'), - hide_skin: +!!utils.deepAccess(currentBidRequest, 'params.hide_skin'), - outstream_options: utils.deepAccess(currentBidRequest, 'params.outstream_options'), - outstream_function: utils.deepAccess(currentBidRequest, 'params.outstream_function') + content_page_url: deepAccess(bidderRequest, 'data.site.page'), + ad_mute: +!!deepAccess(currentBidRequest, 'params.ad_mute'), + hide_skin: +!!deepAccess(currentBidRequest, 'params.hide_skin'), + outstream_options: deepAccess(currentBidRequest, 'params.outstream_options') } }); try { - renderer.setRender(outstreamRender); + renderer.setRender(createOutstreamConfig); renderer.setEventHandlers({ impression: function impression() { - return utils.logMessage('SmartX outstream video impression event'); + return logMessage('SmartX outstream video impression event'); }, loaded: function loaded() { - return utils.logMessage('SmartX outstream video loaded event'); + return logMessage('SmartX outstream video loaded event'); }, ended: function ended() { - return utils.logMessage('SmartX outstream renderer video event'); + return logMessage('SmartX outstream renderer video event'); } }); } catch (err) { - utils.logWarn('Prebid Error calling setRender or setEventHandlers on renderer', err); + logWarn('Prebid Error calling setRender or setEventHandlers on renderer', err); } bid.renderer = renderer; } @@ -340,26 +339,22 @@ export const spec = { } } -function createOutstreamScript(bid) { - const confMinAdWidth = utils.getBidIdParameter('minAdWidth', bid.renderer.config.outstream_options) || 290; - const confMaxAdWidth = utils.getBidIdParameter('maxAdWidth', bid.renderer.config.outstream_options) || 900; - const confStartOpen = utils.getBidIdParameter('startOpen', bid.renderer.config.outstream_options); - const confEndingScreen = utils.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options); - const confTitle = utils.getBidIdParameter('title', bid.renderer.config.outstream_options); - const confSkipOffset = utils.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options); - const confDesiredBitrate = utils.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options); - const elementId = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; +function createOutstreamConfig(bid) { + let confMinAdWidth = getBidIdParameter('minAdWidth', bid.renderer.config.outstream_options) || 290; + let confMaxAdWidth = getBidIdParameter('maxAdWidth', bid.renderer.config.outstream_options) || 900; + let confStartOpen = getBidIdParameter('startOpen', bid.renderer.config.outstream_options) + let confEndingScreen = getBidIdParameter('endingScreen', bid.renderer.config.outstream_options) + let confTitle = getBidIdParameter('title', bid.renderer.config.outstream_options); + let confSkipOffset = getBidIdParameter('skipOffset', bid.renderer.config.outstream_options); + let confDesiredBitrate = getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options); + let confVisibilityThreshold = getBidIdParameter('visibilityThreshold', bid.renderer.config.outstream_options); + let elementId = getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; - utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); + logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); var smartPlayObj = { minAdWidth: confMinAdWidth, maxAdWidth: confMaxAdWidth, - title: confTitle, - skipOffset: confSkipOffset, - startOpen: confStartOpen, - endingScreen: confEndingScreen, - desiredBitrate: confDesiredBitrate, onStartCallback: function (m, n) { try { window.sc_smartIntxtStart(n); @@ -377,39 +372,70 @@ function createOutstreamScript(bid) { }, }; + if (confStartOpen == 'true') { + smartPlayObj.startOpen = true; + } else if (confStartOpen == 'false') { + smartPlayObj.startOpen = false; + } + + if (confEndingScreen == 'true') { + smartPlayObj.endingScreen = true; + } else if (confEndingScreen == 'false') { + smartPlayObj.endingScreen = false; + } + + if (confTitle || (typeof bid.renderer.config.outstream_options.title == 'string' && bid.renderer.config.outstream_options.title == '')) { + smartPlayObj.title = confTitle; + } + + if (confSkipOffset) { + smartPlayObj.skipOffset = confSkipOffset; + } + + if (confDesiredBitrate) { + smartPlayObj.desiredBitrate = confDesiredBitrate; + } + + if (confVisibilityThreshold) { + smartPlayObj.visibilityThreshold = confVisibilityThreshold; + } + smartPlayObj.adResponse = bid.vastContent; const divID = '[id="' + elementId + '"]'; - var script = document.createElement('script'); - script.src = 'https://dco.smartclip.net/?plc=7777778'; - script.type = 'text/javascript'; - script.async = 'true'; - script.onload = script.onreadystatechange = function () { - try { - // eslint-disable-next-line - let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj); - } catch (e) { - utils.logError('[SmartPlay][renderer] Error caught: ' + e); - } - }; - return script; + + try { + // eslint-disable-next-line + let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj); + } catch (e) { + logError('[SMARTX][renderer] Error caught: ' + e); + } + return smartPlayObj; } -function outstreamRender(bid) { - const script = createOutstreamScript(bid); - if (bid.renderer.config.outstream_function != null && typeof bid.renderer.config.outstream_function === 'function') { - bid.renderer.config.outstream_function(bid, script); - } else { - try { - const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); - if (slot && window.document.getElementById(slot)) { - window.document.getElementById(slot).appendChild(script); - } else { - window.document.getElementsByTagName('head')[0].appendChild(script); - } - } catch (err) { - utils.logError('[SMARTX][renderer] Error:' + err.message) +/** + * Get the floor price from bid.params for backward compatibility. + * If not found, then check floor module. + * @param bid A valid bid object + * @returns {*|number} floor price + */ +function getBidFloor(bid) { + let floor = getBidIdParameter('bidfloor', bid.params); + let floorcur = getBidIdParameter('bidfloorcur', bid.params) || 'EUR'; + + if (!floor && isFn(bid.getFloor)) { + const floorObj = bid.getFloor({ + currency: floorcur, + mediaType: '*', + size: '*' + }); + + if (isPlainObject(floorObj) && !isNaN(floorObj.floor) && floorObj.currency === floorcur) { + floor = floorObj.floor; } } + + return floor; } + registerBidder(spec); diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index 223e51763b9..853f06d6baf 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -38,8 +38,8 @@ This adapter requires setup and approval from the smartclip team. maxAdWidth: 900, title: '', skipOffset: 0, - startOpen: true, - endingScreen: true, + startOpen: 'true', + endingScreen: 'true', desiredBitrate: 800, }, } @@ -73,8 +73,8 @@ This adapter requires setup and approval from the smartclip team. maxAdWidth: 900, title: '', skipOffset: 0, - startOpen: true, - endingScreen: true, + startOpen: 'true', + endingScreen: 'true', desiredBitrate: 800, }, user: { diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index 610617155ed..b999d02b059 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -1,11 +1,11 @@ +import { logMessage } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; -import * as utils from '../src/utils.js'; const BIDDER_CODE = 'smartyads'; -const AD_URL = 'https://ssp-nj.webtradehub.com/?c=o&m=multi'; -const URL_SYNC = 'https://ssp-nj.webtradehub.com/?c=o&m=cookie'; +const AD_URL = 'https://n1.smartyads.com/?c=o&m=prebid&secret_key=prebid_js'; +const URL_SYNC = 'https://as.ck-ie.com/prebidjs?p=7c47322e527cf8bdeb7facc1bb03387a'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -29,7 +29,7 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placementId)); + return Boolean(bid.bidId && bid.params && !isNaN(bid.params.sourceid) && !isNaN(bid.params.accountid) && bid.params.host == 'prebid'); }, buildRequests: (validBidRequests = [], bidderRequest) => { @@ -40,7 +40,7 @@ export const spec = { winTop = window.top; } catch (e) { location = winTop.location; - utils.logMessage(e); + logMessage(e); }; let placements = []; let request = { @@ -68,10 +68,11 @@ export const spec = { let bid = validBidRequests[i]; let traff = bid.params.traffic || BANNER placements.push({ - placementId: bid.params.placementId, + placementId: bid.params.sourceid, bidId: bid.bidId, sizes: bid.mediaTypes && bid.mediaTypes[traff] && bid.mediaTypes[traff].sizes ? bid.mediaTypes[traff].sizes : [], - traffic: traff + traffic: traff, + publisherId: bid.params.accountid }); if (bid.schain) { placements.schain = bid.schain; @@ -96,11 +97,23 @@ export const spec = { return response; }, - getUserSyncs: (syncOptions, serverResponses) => { - return [{ - type: 'image', - url: URL_SYNC - }]; + getUserSyncs: (syncOptions, serverResponses = [], gdprConsent = {}, uspConsent = '') => { + let syncs = []; + let { gdprApplies, consentString = '' } = gdprConsent; + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `${URL_SYNC}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&type=iframe&us_privacy=${uspConsent}` + }); + } else { + syncs.push({ + type: 'image', + url: `${URL_SYNC}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&type=image&us_privacy=${uspConsent}` + }); + } + + return syncs } }; diff --git a/modules/smartyadsBidAdapter.md b/modules/smartyadsBidAdapter.md index 4fe4d51a2e6..f078d905e62 100644 --- a/modules/smartyadsBidAdapter.md +++ b/modules/smartyadsBidAdapter.md @@ -23,7 +23,9 @@ Module that connects to SmartyAds' demand sources { bidder: 'smartyads', params: { - placementId: 0, + host: 'prebid', + sourceid: '0', + accountid: '0', traffic: 'native' } } @@ -41,7 +43,9 @@ Module that connects to SmartyAds' demand sources { bidder: 'smartyads', params: { - placementId: 0, + host: 'prebid', + sourceid: '0', + accountid: '0', traffic: 'banner' } } @@ -60,7 +64,9 @@ Module that connects to SmartyAds' demand sources { bidder: 'smartyads', params: { - placementId: 0, + host: 'prebid', + sourceid: '0', + accountid: '0', traffic: 'video' } } diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index fb05298a230..73bd6101670 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { isArray, logError, logWarn, isFn, isPlainObject } from '../src/utils.js'; import { Renderer } from '../src/Renderer.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -29,7 +29,6 @@ export const spec = { var payload = { zoneId: bid.params.zoneId, currencyCode: config.getConfig('currency.adServerCurrency') || 'EUR', - bidfloor: bid.params.bidfloor || 0.0, tagId: bid.adUnitCode, sizes: bid.sizes.map(size => ({ w: size[0], @@ -38,9 +37,19 @@ export const spec = { transactionId: bid.transactionId, timeout: config.getConfig('bidderTimeout'), bidId: bid.bidId, + positionType: bid.params.positionType || '', prebidVersion: '$prebid.version$' }; + const floor = getBidFloor(bid); + if (floor) { + payload.bidfloor = floor; + } + + if (bid.params.bidfloor) { + payload.bidfloor = bid.params.bidfloor; + } + if (bidderRequest && bidderRequest.refererInfo) { payload.pageDomain = bidderRequest.refererInfo.referer || ''; } @@ -70,6 +79,7 @@ export const spec = { try { if (response) { + const dealId = response.dealId || ''; const bidResponse = { requestId: JSON.parse(bidRequest.data).bidId, cpm: response.cpm, @@ -93,10 +103,18 @@ export const spec = { bidResponse['renderer'] = newRenderer(JSON.parse(bidRequest.data), response); } + if (dealId.length > 0) { + bidResponse.dealId = dealId; + } + + bidResponse.meta = {}; + if (response.meta && response.meta.advertiserDomains && isArray(response.meta.advertiserDomains)) { + bidResponse.meta.advertiserDomains = response.meta.advertiserDomains; + } bidResponses.push(bidResponse); } } catch (error) { - utils.logError('Error while parsing smilewanted response', error); + logError('Error while parsing smilewanted response', error); } return bidResponses; }, @@ -152,7 +170,7 @@ function newRenderer(bidRequest, bidResponse) { try { renderer.setRender(outstreamRender); } catch (err) { - utils.logWarn('Prebid Error calling setRender on newRenderer', err); + logWarn('Prebid Error calling setRender on newRenderer', err); } return renderer; } @@ -172,4 +190,24 @@ function outstreamRender(bid) { }); } +/** + * Get the floor price from bid.params for backward compatibility. + * If not found, then check floor module. + * @param bid A valid bid object + * @returns {*|number} floor price + */ +function getBidFloor(bid) { + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: 'banner', + size: bid.sizes.map(size => ({ w: size[0], h: size[1] })) + }); + if (isPlainObject(floorInfo) && !isNaN(floorInfo.floor) && floorInfo.currency === 'USD') { + return parseFloat(floorInfo.floor); + } + } + return null; +} + registerBidder(spec); diff --git a/modules/smmsBidAdapter.js b/modules/smmsBidAdapter.js deleted file mode 100644 index 670caebe61b..00000000000 --- a/modules/smmsBidAdapter.js +++ /dev/null @@ -1,156 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'smms'; -const ENDPOINT_BANNER = 'https://bidder.mediams.mb.softbank.jp/api/v1/prebid/banner'; -const ENDPOINT_NATIVE = 'https://bidder.mediams.mb.softbank.jp/api/v1/prebid/native'; -const COOKIE_SYNC_URL = 'https://bidder.mediams.mb.softbank.jp/api/v1/cookie/gen'; -const SUPPORTED_MEDIA_TYPES = ['banner', 'native']; -const SUPPORTED_CURRENCIES = ['USD', 'JPY']; -const DEFAULT_CURRENCY = 'JPY'; -const NET_REVENUE = true; - -function _encodeURIComponent(a) { - let b = window.encodeURIComponent(a); - b = b.replace(/'/g, '%27'); - return b; -} - -export function _getUrlVars(url) { - let hash; - const myJson = {}; - const hashes = url.slice(url.indexOf('?') + 1).split('&'); - for (let i = 0; i < hashes.length; i++) { - hash = hashes[i].split('='); - myJson[hash[0]] = hash[1]; - } - return myJson; -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - isBidRequestValid: function(bid) { - let valid = !!(bid.params.placementId); - if (valid && bid.params.hasOwnProperty('currency')) { - if (SUPPORTED_CURRENCIES.indexOf(bid.params.currency) === -1) { - utils.logError('Invalid currency type, we support only JPY and USD!'); - valid = false; - } - } - - return valid; - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests, bidderRequest) { - const serverRequests = []; - let refererInfo; - if (bidderRequest && bidderRequest.refererInfo) { - refererInfo = bidderRequest.refererInfo; - } - - // eslint-disable-next-line no-undef - const g = (typeof (geparams) !== 'undefined' && typeof (geparams) == 'object' && geparams) ? geparams : {}; - validBidRequests.forEach((bid, i) => { - let endpoint = ENDPOINT_BANNER; - let data = { - 'placementid': bid.params.placementId, - 'cur': bid.params.hasOwnProperty('currency') ? bid.params.currency : DEFAULT_CURRENCY, - 'ua': navigator.userAgent, - 'adtk': _encodeURIComponent(g.lat ? '0' : '1'), - 'loc': (refererInfo && refererInfo.referer) ? refererInfo.referer : '', - 'topframe': (window.parent == window.self) ? 1 : 0, - 'sw': screen && screen.width, - 'sh': screen && screen.height, - 'cb': Math.floor(Math.random() * 99999999999), - 'tpaf': 1, - 'cks': 1, - 'requestid': bid.bidId, - 'referer': (refererInfo && refererInfo.referer) ? refererInfo.referer : '' - }; - - if (bid.hasOwnProperty('nativeParams')) { - endpoint = ENDPOINT_NATIVE; - data.tkf = 1; // return url tracker - data.ad_track = '1'; - data.apiv = '1.1.0'; - } - - serverRequests.push({ - method: 'GET', - url: endpoint, - data: utils.parseQueryStringParameters(data) - }); - }); - - return serverRequests; - }, - interpretResponse: function(serverResponse, request) { - const data = _getUrlVars(request.data) - const successBid = serverResponse.body || {}; - let bidResponses = []; - if (successBid.hasOwnProperty(data.placementid)) { - let bid = successBid[data.placementid] - let bidResponse = { - requestId: bid.requestid, - cpm: bid.price, - creativeId: bid.creativeId, - currency: bid.cur, - netRevenue: NET_REVENUE, - ttl: 700 - }; - - if (bid.hasOwnProperty('title')) { // it is native ad response - bidResponse.mediaType = 'native' - bidResponse.native = { - title: bid.title, - body: bid.description, - cta: bid.cta, - sponsoredBy: bid.advertiser, - clickUrl: _encodeURIComponent(bid.landingURL), - impressionTrackers: bid.trackings, - } - if (bid.screenshots) { - bidResponse.native.image = { - url: bid.screenshots.url, - height: bid.screenshots.height, - width: bid.screenshots.width, - } - } - if (bid.icon) { - bidResponse.native.icon = { - url: bid.icon.url, - height: bid.icon.height, - width: bid.icon.width, - } - } - } else { - bidResponse.ad = bid.adm - bidResponse.width = bid.width - bidResponse.height = bid.height - } - - bidResponses.push(bidResponse); - } - - return bidResponses; - }, - getUserSyncs: function(syncOptions, serverResponses) { - let syncs = [] - syncs.push({ - type: 'image', - url: COOKIE_SYNC_URL - }) - - return syncs; - }, - onTimeout: function(timeoutData) {}, - onBidWon: function(bid) {}, - onSetTargeting: function(bid) {} -} -registerBidder(spec); diff --git a/modules/somoBidAdapter.js b/modules/somoBidAdapter.js deleted file mode 100644 index c6e31790005..00000000000 --- a/modules/somoBidAdapter.js +++ /dev/null @@ -1,289 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import includes from 'core-js-pure/features/array/includes.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; - -const VIDEO_TARGETING = ['mimes', 'minduration', 'maxduration', 'protocols', - 'startdelay', 'linearity', 'skip', 'delivery', - 'pos', 'api', 'ext', 'battr']; -const BANNER_TARGETING = ['battr', 'btype', 'pos', 'mimes', 'ext']; - -const SITE_TARGETING = ['name', 'domain', 'cat', 'keywords', 'content'] -const APP_TARGETING = ['name', 'bundle', 'domain', 'storeUrl', 'cat', 'ver', 'keywords', 'content'] - -export const spec = { - - code: 'somo', - - supportedMediaTypes: [BANNER, VIDEO], - aliases: ['somoaudience'], - - isBidRequestValid: bid => ( - !!(bid && bid.params && bid.params.placementId) - ), - - buildRequests: function(bidRequests, bidderRequest) { - return bidRequests.map(bidRequest => { - let da = openRtbRequest(bidRequest, bidderRequest); - - return { - method: 'POST', - url: 'https://publisher-east.mobileadtrading.com/rtb/bid?s=' + bidRequest.params.placementId.toString(), - data: da, - bidRequest: bidRequest - }; - }); - }, - - interpretResponse(response, request) { - return bidResponseAvailable(request, response); - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent) => { - const syncs = []; - var url = 'https://publisher-east.mobileadtrading.com/usersync'; - - if (syncOptions.pixelEnabled) { - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - // add 'gdpr' only if 'gdprApplies' is defined - if (typeof gdprConsent.gdprApplies === 'boolean') { - url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } - } - syncs.push({ - type: 'image', - url: url - }); - } - return syncs; - } -}; - -function bidResponseAvailable(bidRequest, bidResponse) { - let bidResponses = []; - - if (bidResponse.body) { - let bidData = bidResponse.body.seatbid[0].bid[0]; - const bid = { - requestId: bidResponse.body.id, - cpm: bidData.price, - width: bidData.w, - height: bidData.h, - ad: bidData.adm, - ttl: 360, - creativeId: bidData.crid, - adId: bidData.impid, - netRevenue: false, - currency: 'USD', - adUnitCode: bidRequest.bidRequest.adUnitCode - }; - if (isVideo(bidRequest.bidRequest)) { - bid.vastXml = bidData.adm; - bid.mediaType = 'video'; - } else { - bid.ad = bidData.adm; - bid.mediaType = 'banner'; - } - bidResponses.push(bid); - } - return bidResponses; -} - -function openRtbRequest(bidRequest, bidderRequest) { - var openRtbRequest = { - id: bidRequest.bidId, - imp: [openRtbImpression(bidRequest)], - at: 1, - tmax: 400, - site: openRtbSite(bidRequest, bidderRequest), - app: openRtbApp(bidRequest), - device: openRtbDevice(), - bcat: openRtbBCat(bidRequest), - badv: openRtbBAdv(bidRequest), - ext: { - prebid: '$prebid.version$', - }, - }; - if (typeof bidderRequest !== 'undefined') { - openRtbRequest = populateOpenRtbGdpr(bidderRequest.gdprConsent, openRtbRequest); - } - - return openRtbRequest; -} - -function populateOpenRtbGdpr(gdpr, bidRequest) { - if (gdpr && bidRequest && 'gdprApplies' in gdpr) { - if (!('reqs' in bidRequest)) { - bidRequest.reqs = {}; - } - if (!('ext' in bidRequest.reqs)) { - bidRequest.reqs.ext = {}; - } - bidRequest.reqs.ext.gdpr = gdpr.gdprApplies; - - if ('consentString' in gdpr) { - if (!('user' in bidRequest)) { - bidRequest.user = {}; - } - if (!('ext' in bidRequest.user)) { - bidRequest.user.ext = {}; - } - bidRequest.user.ext.consent = gdpr.consentString; - } - } - - return bidRequest; -} - -function openRtbImpression(bidRequest) { - const imp = { - 'id': bidRequest.bidId, - bidfloor: bidRequest.params.bidfloor || 0, - }; - if (isVideo(bidRequest)) { - imp.video = {}; - if (bidRequest.mediaTypes && - bidRequest.mediaTypes.video && - bidRequest.mediaTypes.video.sizes) { - const sizes = getSizes(bidRequest.mediaTypes.video.sizes); - imp.video.w = sizes[0]; - imp.video.h = sizes[1]; - } - if (bidRequest.params.video) { - Object.keys(bidRequest.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => imp.video[param] = bidRequest.params.video[param]); - } - } else { - imp.banner = { - topframe: 0 - }; - if (bidRequest.mediaTypes && - bidRequest.mediaTypes.banner && - bidRequest.mediaTypes.banner.sizes) { - const sizes = getSizes(bidRequest.mediaTypes.banner.sizes); - imp.banner.w = sizes[0]; - imp.banner.h = sizes[1]; - } - if (bidRequest.params.banner) { - Object.keys(bidRequest.params.banner) - .filter(param => includes(BANNER_TARGETING, param)) - .forEach(param => imp.banner[param] = bidRequest.params.banner[param]); - } - } - return imp; -} - -function isApp(bidRequest) { - if (bidRequest.params.app) { - return true; - } else { - return false; - } -} - -function openRtbSite(bidRequest, bidderRequest) { - if (!isApp(bidRequest)) { - const site = {}; - - if (bidderRequest && bidderRequest.refererInfo) { - site.ref = bidderRequest.refererInfo.referer; - site.page = bidderRequest.refererInfo.canonicalUrl; - } - - if (bidRequest.params.site) { - Object.keys(bidRequest.params.site) - .filter(param => includes(SITE_TARGETING, param)) - .forEach(param => site[param] = bidRequest.params.site[param]); - } - if (typeof site.domain === 'undefined' && - typeof site.page !== 'undefined') { - if (typeof window.URL === 'function') { - site.domain = (new window.URL(site.page)).hostname; - } else { - site.domain = getDomainFromUrl(site.page); - } - } - - return site; - } else { - return null; - } -} - -function getDomainFromUrl(url) { - var domain = url; - - if (url.indexOf('//') > -1) { - domain = url.split('/')[2]; - } else { - domain = url.split('/')[0]; - } - - domain = domain.split(':')[0]; - domain = domain.split('?')[0]; - - return domain; -} - -function openRtbApp(bidRequest) { - if (isApp(bidRequest)) { - const app = { - - } - Object.keys(bidRequest.params.app) - .filter(param => includes(APP_TARGETING, param)) - .forEach(param => app[param] = bidRequest.params.app[param]); - - return app; - } else { - return null; - } -} - -function openRtbDevice() { - return { - ip: 'check', - ua: navigator.userAgent, - language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), - }; -} - -function openRtbBCat(bidRequest) { - if (utils.isArray(bidRequest.params.bcat)) { - return bidRequest.params.bcat; - } - return []; -} - -function openRtbBAdv(bidRequest) { - if (utils.isArray(bidRequest.params.badv)) { - return bidRequest.params.badv; - } - return []; -} - -function isVideo(format) { - return utils.deepAccess(format, 'mediaTypes.video') || format.mediaType == 'video'; -} - -/* Turn bid request sizes into compatible format */ -function getSizes(requestSizes) { - let width = 0; - let height = 0; - if (utils.isArray(requestSizes) && requestSizes.length === 2 && - !utils.isArray(requestSizes[0])) { - width = parseInt(requestSizes[0], 10); - height = parseInt(requestSizes[1], 10); - } else if (typeof requestSizes === 'object') { - for (let i = 0; i < requestSizes.length; i++) { - let size = requestSizes[i]; - width = parseInt(size[0], 10); - height = parseInt(size[1], 10); - break; - } - } - return [width, height]; -} - -registerBidder(spec); diff --git a/modules/sonobiAnalyticsAdapter.js b/modules/sonobiAnalyticsAdapter.js index d69276e915c..0de6647149a 100644 --- a/modules/sonobiAnalyticsAdapter.js +++ b/modules/sonobiAnalyticsAdapter.js @@ -1,9 +1,9 @@ +import { deepClone, logInfo, logError } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import {ajaxBuilder} from '../src/ajax.js'; -const utils = require('../src/utils.js'); let ajax = ajaxBuilder(0); const DEFAULT_EVENT_URL = 'apex.go.sonobi.com/keymaker'; @@ -91,7 +91,7 @@ function addToAuctionQueue(auctionId, id) { if (item.bidid !== id) { return true; } return auction.stats[id].data.p !== item.p; }); - auction.queue.push(utils.deepClone(auction.stats[id].data)); + auction.queue.push(deepClone(auction.stats[id].data)); if (!auction.qTimeout) { auction.qTimeout = setTimeout(() => { sendQueue(auctionId); @@ -102,18 +102,18 @@ function updateBidStats(auctionId, id, data) { let auction = auctionCache[auctionId]; auction.stats[id].data = {...auction.stats[id].data, ...data}; addToAuctionQueue(auctionId, id); - logInfo('Updated Bid Stats: ', auction.stats[id]); + _logInfo('Updated Bid Stats: ', auction.stats[id]); return auction.stats[id]; } function handleOtherEvents(eventType, args) { - logInfo('Other Event: ' + eventType, args); + _logInfo('Other Event: ' + eventType, args); } function handlerAuctionInit(args) { auctionCache[args.auctionId] = buildAuctionEntity(args); deleteOldAuctions(); - logInfo('Auction Init', args); + _logInfo('Auction Init', args); } function handlerBidRequested(args) { let auction = auctionCache[args.auctionId]; @@ -127,14 +127,14 @@ function handlerBidRequested(args) { addToAuctionQueue(args.auctionId, built.bidid); }) - logInfo('Bids Requested ', data); + _logInfo('Bids Requested ', data); } function handlerBidAdjustment(args) { - logInfo('Bid Adjustment', args); + _logInfo('Bid Adjustment', args); } function handlerBidderDone(args) { - logInfo('Bidder Done', args); + _logInfo('Bidder Done', args); } function handlerAuctionEnd(args) { @@ -152,24 +152,24 @@ function handlerAuctionEnd(args) { updateBidStats(args.auctionId, bidId, {response: 4}); } }) - logInfo('Auction End', args); - logInfo('Auction Cache', auctionCache[args.auctionId].stats); + _logInfo('Auction End', args); + _logInfo('Auction Cache', auctionCache[args.auctionId].stats); } function handlerBidWon(args) { let {auctionId, requestId} = args; let res = updateBidStats(auctionId, requestId, {p: 3, response: 6}); - logInfo('Bid Won ', args); - logInfo('Bid Update Result: ', res); + _logInfo('Bid Won ', args); + _logInfo('Bid Update Result: ', res); } function handlerBidResponse(args) { let {auctionId, requestId, cpm, size, timeToRespond} = args; updateBidStats(auctionId, requestId, {bid: cpm, s: size, jsLatency: timeToRespond, latency: timeToRespond, p: 2, response: 9}); - logInfo('Bid Response ', args); + _logInfo('Bid Response ', args); } function handlerBidTimeout(args) { let {auctionId, bidId} = args; - logInfo('Bid Timeout ', args); + _logInfo('Bid Timeout ', args); updateBidStats(auctionId, bidId, {p: 2, response: 0, latency: args.timeout, jsLatency: args.timeout}); } let sonobiAdapter = Object.assign(adapter({url: DEFAULT_EVENT_URL, analyticsType}), { @@ -211,7 +211,7 @@ sonobiAdapter.originEnableAnalytics = sonobiAdapter.enableAnalytics; sonobiAdapter.enableAnalytics = function (config) { if (this.initConfig(config)) { - logInfo('Analytics adapter enabled', initOptions); + _logInfo('Analytics adapter enabled', initOptions); sonobiAdapter.originEnableAnalytics(config); } }; @@ -219,17 +219,17 @@ sonobiAdapter.enableAnalytics = function (config) { sonobiAdapter.initConfig = function (config) { let isCorrectConfig = true; initOptions = {}; - initOptions.options = utils.deepClone(config.options); + initOptions.options = deepClone(config.options); initOptions.pubId = initOptions.options.pubId || null; initOptions.siteId = initOptions.options.siteId || null; initOptions.delay = initOptions.options.delay || QUEUE_TIMEOUT_DEFAULT; if (!initOptions.pubId) { - logError('"options.pubId" is empty'); + _logError('"options.pubId" is empty'); isCorrectConfig = false; } if (!initOptions.siteId) { - logError('"options.siteId" is empty'); + _logError('"options.siteId" is empty'); isCorrectConfig = false; } @@ -247,7 +247,7 @@ sonobiAdapter.sendData = function (auction, data) { let url = 'https://' + initOptions.server + '?pageviewid=' + auction.id + '&corscred=1&pubId=' + initOptions.pubId + '&siteId=' + initOptions.siteId; ajax( url, - function () { logInfo('Auction [' + auction.id + '] sent ', data); }, + function () { _logInfo('Auction [' + auction.id + '] sent ', data); }, JSON.stringify(data), { method: 'POST', @@ -257,12 +257,12 @@ sonobiAdapter.sendData = function (auction, data) { ); } -function logInfo(message, meta) { - utils.logInfo(buildLogMessage(message), meta); +function _logInfo(message, meta) { + logInfo(buildLogMessage(message), meta); } -function logError(message) { - utils.logError(buildLogMessage(message)); +function _logError(message) { + logError(buildLogMessage(message)); } function buildLogMessage(message) { diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index e5bd76eba9f..c5fc07320d8 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,5 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, deepClone } from '../src/utils.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, deepClone, getGptSlotInfoForAdUnitCode, isFn, isPlainObject } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; @@ -60,11 +60,11 @@ export const spec = { if (/^[\/]?[\d]+[[\/].+[\/]?]?$/.test(slotIdentifier)) { slotIdentifier = slotIdentifier.charAt(0) === '/' ? slotIdentifier : '/' + slotIdentifier; return { - [`${slotIdentifier}|${bid.bidId}`]: `${_validateSize(bid)}${_validateFloor(bid)}` + [`${slotIdentifier}|${bid.bidId}`]: `${_validateSize(bid)}${_validateFloor(bid)}${_validateGPID(bid)}` } } else if (/^[0-9a-fA-F]{20}$/.test(slotIdentifier) && slotIdentifier.length === 20) { return { - [bid.bidId]: `${slotIdentifier}|${_validateSize(bid)}${_validateFloor(bid)}` + [bid.bidId]: `${slotIdentifier}|${_validateSize(bid)}${_validateFloor(bid)}${_validateGPID(bid)}` } } else { logError(`The ad unit code or Sonobi Placement id for slot ${bid.bidId} is invalid`); @@ -83,8 +83,15 @@ export const spec = { 'lib_name': 'prebid', 'lib_v': '$prebid.version$', 'us': 0, + }; + const fpd = config.getConfig('ortb2'); + + if (fpd) { + payload.fpd = JSON.stringify(fpd); + } + if (config.getConfig('userSync') && config.getConfig('userSync').syncsPerBidder) { payload.us = config.getConfig('userSync').syncsPerBidder; } @@ -199,6 +206,10 @@ export const spec = { width = 1, height = 1 ] = bid.sbi_size.split('x'); + let aDomains = []; + if (bid.sbi_adomain) { + aDomains = [bid.sbi_adomain] + } const bids = { requestId: bidId, cpm: Number(bid.sbi_mouse), @@ -209,7 +220,10 @@ export const spec = { creativeId: bid.sbi_crid || bid.sbi_aid, aid: bid.sbi_aid, netRevenue: true, - currency: 'USD' + currency: 'USD', + meta: { + advertiserDomains: aDomains + } }; if (bid.sbi_dozer) { @@ -223,6 +237,7 @@ export const spec = { delete bids.width; delete bids.height; } else if (mediaType === 'outstream' && bidRequest) { + delete bids.ad; // Some pubs expect bids.ad to be a vast xml structure, we have a vatUrl so lets delete this. bids.mediaType = 'video'; bids.vastUrl = createCreative(bidResponse.sbi_dc, bid.sbi_aid); bids.renderer = newRenderer(bidRequest.adUnitCode, bids, deepAccess( @@ -296,12 +311,23 @@ function _validateSlot (bid) { } function _validateFloor (bid) { - if (bid.params.floor) { - return `|f=${bid.params.floor}`; + const floor = getBidFloor(bid); + + if (floor) { + return `|f=${floor}`; } return ''; } +function _validateGPID(bid) { + const gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') || deepAccess(getGptSlotInfoForAdUnitCode(bid.adUnitCode), 'gptSlot') || bid.params.ad_unit; + + if (gpid) { + return `|gpid=${gpid}` + } + return '' +} + const _creative = (mediaType, referer) => (sbiDc, sbiAid) => { if (mediaType === 'video' || mediaType === 'outstream') { return _videoCreative(sbiDc, sbiAid, referer) @@ -391,4 +417,21 @@ function _iframeAllowed() { return userSync.canBidderRegisterSync('iframe', BIDDER_CODE); } +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.floor) ? bid.params.floor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return ''; +} + registerBidder(spec); diff --git a/modules/sortableAnalyticsAdapter.js b/modules/sortableAnalyticsAdapter.js index 73ce1393c23..4580ce6dbb8 100644 --- a/modules/sortableAnalyticsAdapter.js +++ b/modules/sortableAnalyticsAdapter.js @@ -1,10 +1,11 @@ +import { logInfo, getParameterByName, getOldestHighestCpmBid } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -import * as utils from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {getGlobal} from '../src/prebidGlobal.js'; import { config } from '../src/config.js'; +import {bidderSettings} from '../src/bidderSettings.js'; const DEFAULT_PROTOCOL = 'https'; const DEFAULT_HOST = 'pa.deployads.com'; @@ -160,7 +161,7 @@ function getSessionParams() { sessionParams = paramsFromStorage && stillValid(paramsFromStorage) ? paramsFromStorage : null; } sessionParams = sessionParams || {'created': +new Date(), 'sessionId': generateRandomId()}; - const urlParams = UTM_PARAMS.map(utils.getParameterByName); + const urlParams = UTM_PARAMS.map(getParameterByName); if (UTM_PARAMS.every(key => !sessionParams[key])) { UTM_PARAMS.forEach((v, i) => sessionParams[v] = urlParams[i] || sessionParams[v]); sessionParams.created = +new Date(); @@ -182,11 +183,11 @@ function getFactor(bidder) { } function getBiddersFactors() { - const pb = getGlobal(); const result = {}; - if (pb && pb.bidderSettings) { - Object.keys(pb.bidderSettings).forEach(bidderKey => { - const bidder = pb.bidderSettings[bidderKey]; + const settings = bidderSettings.getSettings(); + if (settings) { + Object.keys(settings).forEach(bidderKey => { + const bidder = settings[bidderKey]; const factor = getFactor(bidder); if (factor !== null) { result[bidderKey] = factor; @@ -318,7 +319,7 @@ function sendEvents(events) { 'method': 'POST', 'withCredentials': true }; - const onSend = () => utils.logInfo('Sortable Analytics data sent'); + const onSend = () => logInfo('Sortable Analytics data sent'); ajax(url, onSend, JSON.stringify(mergedEvents), options); } @@ -444,7 +445,7 @@ function handleAuctionEnd(event) { const events = Object.keys(adUnits).map(adUnitCode => { const bidderKeys = Object.keys(auction.adUnits[adUnitCode].bids); const bids = bidderKeys.map(bidderCode => auction.adUnits[adUnitCode].bids[bidderCode]); - const highestBid = bids.length ? bids.reduce(utils.getOldestHighestCpmBid) : null; + const highestBid = bids.length ? bids.reduce(getOldestHighestCpmBid) : null; return bidderKeys.map(bidderCode => { const bid = auction.adUnits[adUnitCode].bids[bidderCode]; if (highestBid && highestBid.cpm) { @@ -507,7 +508,7 @@ sortableAnalyticsAdapter.originEnableAnalytics = sortableAnalyticsAdapter.enable sortableAnalyticsAdapter.enableAnalytics = function (setupConfig) { if (this.initConfig(setupConfig)) { - utils.logInfo('Sortable Analytics adapter enabled'); + logInfo('Sortable Analytics adapter enabled'); sortableAnalyticsAdapter.originEnableAnalytics(setupConfig); } }; diff --git a/modules/sortableBidAdapter.js b/modules/sortableBidAdapter.js index 6989abff143..15246a10eab 100644 --- a/modules/sortableBidAdapter.js +++ b/modules/sortableBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { _each, logError, isFn, isPlainObject, isNumber, isStr, deepAccess, parseUrl, _map, getUniqueIdentifierStr, createTrackPixelHtml } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -55,7 +55,7 @@ function buildNativeRequest(nativeMediaType) { assets.push(setAssetRequired(sponsoredBy, {data: {type: 1}})); } - utils._each(assets, (asset, id) => asset.id = id); + _each(assets, (asset, id) => asset.id = id); return { ver: '1', request: JSON.stringify({ @@ -70,7 +70,7 @@ function tryParseNativeResponse(adm) { try { native = JSON.parse(adm); } catch (e) { - utils.logError('Sortable bid adapter unable to parse native bid response:\n\n' + e); + logError('Sortable bid adapter unable to parse native bid response:\n\n' + e); } return native && native.native; } @@ -92,7 +92,7 @@ function interpretNativeResponse(response) { if (response.link) { native.clickUrl = response.link.url; } - utils._each(response.assets, asset => { + _each(response.assets, asset => { switch (asset.id) { case 1: native.title = asset.title.text; @@ -118,9 +118,9 @@ function interpretNativeResponse(response) { } function transformSyncs(responses, type, syncs) { - utils._each(responses, res => { + _each(responses, res => { if (res.body && res.body.ext && res.body.ext.sync_dsps && res.body.ext.sync_dsps.length) { - utils._each(res.body.ext.sync_dsps, sync => { + _each(res.body.ext.sync_dsps, sync => { if (sync[0] === type && sync[1]) { syncs.push({type, url: sync[1]}); } @@ -129,6 +129,24 @@ function transformSyncs(responses, type, syncs) { }); } +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.floor ? bid.params.floor : null; + } + + // MediaType and Size will automatically get set for us if the bid only has + // one media type or one size. + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, NATIVE, VIDEO], @@ -136,42 +154,37 @@ export const spec = { isBidRequestValid: function(bid) { const sortableConfig = config.getConfig('sortable'); const haveSiteId = (sortableConfig && !!sortableConfig.siteId) || bid.params.siteId; - const validFloor = !bid.params.floor || utils.isNumber(bid.params.floor); - const validSize = /\d+x\d+/; - const validFloorSizeMap = !bid.params.floorSizeMap || - (utils.isPlainObject(bid.params.floorSizeMap) && - Object.keys(bid.params.floorSizeMap).every(size => - size.match(validSize) && utils.isNumber(bid.params.floorSizeMap[size]) - )) + const floor = getBidFloor(bid); + const validFloor = !floor || isNumber(floor); const validKeywords = !bid.params.keywords || - (utils.isPlainObject(bid.params.keywords) && + (isPlainObject(bid.params.keywords) && Object.keys(bid.params.keywords).every(key => - utils.isStr(key) && utils.isStr(bid.params.keywords[key]) + isStr(key) && isStr(bid.params.keywords[key]) )) const isBanner = !bid.mediaTypes || bid.mediaTypes[BANNER] || !(bid.mediaTypes[NATIVE] || bid.mediaTypes[VIDEO]); - const bannerSizes = isBanner ? utils.deepAccess(bid, `mediaType.${BANNER}.sizes`) || bid.sizes : null; - return !!(bid.params.tagId && haveSiteId && validFloor && validFloorSizeMap && validKeywords && (!isBanner || - (bannerSizes && bannerSizes.length > 0 && bannerSizes.every(sizeArr => sizeArr.length == 2 && sizeArr.every(num => utils.isNumber(num)))))); + const bannerSizes = isBanner ? deepAccess(bid, `mediaType.${BANNER}.sizes`) || bid.sizes : null; + return !!(bid.params.tagId && haveSiteId && validFloor && validKeywords && (!isBanner || + (bannerSizes && bannerSizes.length > 0 && bannerSizes.every(sizeArr => sizeArr.length == 2 && sizeArr.every(num => isNumber(num)))))); }, buildRequests: function(validBidReqs, bidderRequest) { const sortableConfig = config.getConfig('sortable') || {}; const globalSiteId = sortableConfig.siteId; - let loc = utils.parseUrl(bidderRequest.refererInfo.referer); + let loc = parseUrl(bidderRequest.refererInfo.referer); - const sortableImps = utils._map(validBidReqs, bid => { + const sortableImps = _map(validBidReqs, bid => { const rv = { id: bid.bidId, tagid: bid.params.tagId, ext: {} }; - const bannerMediaType = utils.deepAccess(bid, `mediaTypes.${BANNER}`); - const nativeMediaType = utils.deepAccess(bid, `mediaTypes.${NATIVE}`); - const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); + const bannerMediaType = deepAccess(bid, `mediaTypes.${BANNER}`); + const nativeMediaType = deepAccess(bid, `mediaTypes.${NATIVE}`); + const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); if (bannerMediaType || !(nativeMediaType || videoMediaType)) { const bannerSizes = (bannerMediaType && bannerMediaType.sizes) || bid.sizes; rv.banner = { - format: utils._map(bannerSizes, ([width, height]) => ({w: width, h: height})) + format: _map(bannerSizes, ([width, height]) => ({w: width, h: height})) }; } if (nativeMediaType) { @@ -180,9 +193,9 @@ export const spec = { if (videoMediaType && videoMediaType.context === 'instream') { const video = {placement: 1}; video.mimes = videoMediaType.mimes || []; - video.minduration = utils.deepAccess(bid, 'params.video.minduration') || 10; - video.maxduration = utils.deepAccess(bid, 'params.video.maxduration') || 60; - const startDelay = utils.deepAccess(bid, 'params.video.startdelay'); + video.minduration = deepAccess(bid, 'params.video.minduration') || 10; + video.maxduration = deepAccess(bid, 'params.video.maxduration') || 60; + const startDelay = deepAccess(bid, 'params.video.startdelay'); if (startDelay != null) { video.startdelay = startDelay; } @@ -202,27 +215,26 @@ export const spec = { } rv.video = video; } - if (bid.params.floor) { - rv.bidfloor = bid.params.floor; + const floor = getBidFloor(bid); + if (floor) { + rv.floor = floor; } if (bid.params.keywords) { rv.ext.keywords = bid.params.keywords; } if (bid.params.bidderParams) { - utils._each(bid.params.bidderParams, (params, partner) => { + _each(bid.params.bidderParams, (params, partner) => { rv.ext[partner] = params; }); } - if (bid.params.floorSizeMap) { - rv.ext.floorSizeMap = bid.params.floorSizeMap; - } + rv.ext.gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); return rv; }); const gdprConsent = bidderRequest && bidderRequest.gdprConsent; const bidUserId = validBidReqs[0].userId; const eids = createEidsArray(bidUserId); const sortableBidReq = { - id: utils.getUniqueIdentifierStr(), + id: getUniqueIdentifierStr(), imp: sortableImps, source: { ext: { @@ -279,8 +291,8 @@ export const spec = { const { body: {id, seatbid} } = serverResponse; const sortableBids = []; if (id && seatbid) { - utils._each(seatbid, seatbid => { - utils._each(seatbid.bid, bid => { + _each(seatbid, seatbid => { + _each(seatbid.bid, bid => { const bidObj = { requestId: bid.impid, cpm: parseFloat(bid.price), @@ -291,10 +303,13 @@ export const spec = { currency: 'USD', netRevenue: true, mediaType: BANNER, - ttl: 60 + ttl: 60, + meta: { + advertiserDomains: bid.adomain || [] + } }; if (bid.adm) { - const adFormat = utils.deepAccess(bid, 'ext.ad_format') + const adFormat = deepAccess(bid, 'ext.ad_format') if (adFormat === 'native') { let native = tryParseNativeResponse(bid.adm); if (!native) { @@ -309,7 +324,7 @@ export const spec = { bidObj.mediaType = BANNER; bidObj.ad = bid.adm; if (bid.nurl) { - bidObj.ad += utils.createTrackPixelHtml(decodeURIComponent(bid.nurl)); + bidObj.ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); } } } else if (bid.nurl) { diff --git a/modules/sovrnAnalyticsAdapter.js b/modules/sovrnAnalyticsAdapter.js index 0d2576edb05..aee7ddd2690 100644 --- a/modules/sovrnAnalyticsAdapter.js +++ b/modules/sovrnAnalyticsAdapter.js @@ -1,8 +1,8 @@ +import { logError, timestamp } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js' import adaptermanager from '../src/adapterManager.js' import CONSTANTS from '../src/constants.json' import {ajaxBuilder} from '../src/ajax.js' -import * as utils from '../src/utils.js' import {config} from '../src/config.js' import find from 'core-js-pure/features/array/find.js' import includes from 'core-js-pure/features/array/includes.js' @@ -112,7 +112,7 @@ sovrnAnalyticsAdapter.enableAnalytics = function (config) { if (config && config.options && (config.options.sovrnId || config.options.affiliateId)) { sovrnId = config.options.sovrnId || config.options.affiliateId; } else { - utils.logError('Need Sovrn Id to log auction results. Please contact a Sovrn representative if you do not know your Sovrn Id.') + logError('Need Sovrn Id to log auction results. Please contact a Sovrn representative if you do not know your Sovrn Id.') return } sovrnAnalyticsAdapter.sovrnId = sovrnId; @@ -149,7 +149,7 @@ class BidWinner { * Sends the auction to the the ingest server */ send() { - this.body.ts = utils.timestamp() + this.body.ts = timestamp() ajax( pbaUrl, null, @@ -269,7 +269,7 @@ class AuctionData { Object.keys(maxBids).forEach(unit => { maxBids[unit].isAuctionWinner = true }) - this.auction.ts = utils.timestamp() + this.auction.ts = timestamp() ajax( pbaUrl, () => { @@ -311,7 +311,7 @@ class LogError { if (ErrorEvent.data && this.error.data.ad) { delete this.error.data.ad } - this.error.ts = utils.timestamp() + this.error.ts = timestamp() ajax( pbaUrl, null, diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 708cb110dbd..4ca8f03c6b4 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -1,12 +1,38 @@ -import * as utils from '../src/utils.js' +import { _each, getBidIdParameter, isArray, deepClone, parseUrl, getUniqueIdentifierStr, deepSetValue, logError, deepAccess, isInteger, logWarn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' -import { BANNER } from '../src/mediaTypes.js' -import { createEidsArray } from './userId/eids.js'; -import {config} from '../src/config.js'; +import { ADPOD, BANNER, VIDEO } from '../src/mediaTypes.js' +import { createEidsArray } from './userId/eids.js' +import {config} from '../src/config.js' + +const ORTB_VIDEO_PARAMS = { + 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), + 'minduration': (value) => isInteger(value), + 'maxduration': (value) => isInteger(value), + 'protocols': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 10), + 'w': (value) => isInteger(value), + 'h': (value) => isInteger(value), + 'startdelay': (value) => isInteger(value), + 'placement': (value) => isInteger(value) && value >= 1 && value <= 5, + 'linearity': (value) => [1, 2].indexOf(value) !== -1, + 'skip': (value) => [0, 1].indexOf(value) !== -1, + 'skipmin': (value) => isInteger(value), + 'skipafter': (value) => isInteger(value), + 'sequence': (value) => isInteger(value), + 'battr': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 17), + 'maxextended': (value) => isInteger(value), + 'minbitrate': (value) => isInteger(value), + 'maxbitrate': (value) => isInteger(value), + 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, + 'playbackmethod': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6), + 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, + 'delivery': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 3), + 'pos': (value) => isInteger(value) && value >= 1 && value <= 7, + 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6) +} export const spec = { code: 'sovrn', - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], gvlid: 13, /** @@ -15,7 +41,12 @@ export const spec = { * @return boolean for whether or not a bid is valid */ isBidRequestValid: function(bid) { - return !!(bid.params.tagid && !isNaN(parseFloat(bid.params.tagid)) && isFinite(bid.params.tagid)) + return !!( + bid.params.tagid && + !isNaN(parseFloat(bid.params.tagid)) && + isFinite(bid.params.tagid) && + deepAccess(bid, 'mediaTypes.video.context') !== ADPOD + ) }, /** @@ -32,7 +63,7 @@ export const spec = { let tpid = [] let criteoId; - utils._each(bidReqs, function (bid) { + _each(bidReqs, function (bid) { if (!eids && bid.userId) { eids = createEidsArray(bid.userId) eids.forEach(function (id) { @@ -48,34 +79,41 @@ export const spec = { if (bid.schain) { schain = schain || bid.schain } - iv = iv || utils.getBidIdParameter('iv', bid.params) + iv = iv || getBidIdParameter('iv', bid.params) - let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes - bidSizes = ((utils.isArray(bidSizes) && utils.isArray(bidSizes[0])) ? bidSizes : [bidSizes]) - bidSizes = bidSizes.filter(size => utils.isArray(size)) - const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})) const floorInfo = (bid.getFloor && typeof bid.getFloor === 'function') ? bid.getFloor({ currency: 'USD', - mediaType: 'banner', + mediaType: bid.mediaTypes && bid.mediaTypes.banner ? 'banner' : 'video', size: '*' }) : {} - floorInfo.floor = floorInfo.floor || utils.getBidIdParameter('bidfloor', bid.params) + floorInfo.floor = floorInfo.floor || getBidIdParameter('bidfloor', bid.params) const imp = { adunitcode: bid.adUnitCode, id: bid.bidId, - banner: { + tagid: String(getBidIdParameter('tagid', bid.params)), + bidfloor: floorInfo.floor + } + + if (deepAccess(bid, 'mediaTypes.banner')) { + let bidSizes = deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes + bidSizes = (isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes] + bidSizes = bidSizes.filter(size => isArray(size)) + const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})) + + imp.banner = { format: processedSizes, w: 1, h: 1, - }, - tagid: String(utils.getBidIdParameter('tagid', bid.params)), - bidfloor: floorInfo.floor + }; + } + if (deepAccess(bid, 'mediaTypes.video')) { + imp.video = _buildVideoRequestObj(bid); } - imp.ext = utils.getBidIdParameter('ext', bid.ortb2Imp) || undefined + imp.ext = getBidIdParameter('ext', bid.ortb2Imp) || undefined - const segmentsString = utils.getBidIdParameter('segments', bid.params) + const segmentsString = getBidIdParameter('segments', bid.params) if (segmentsString) { imp.ext = imp.ext || {} imp.ext.deals = segmentsString.split(',').map(deal => deal.trim()) @@ -83,15 +121,15 @@ export const spec = { sovrnImps.push(imp) }) - const fpd = config.getConfig('ortb2') || {} + const fpd = deepClone(config.getConfig('ortb2')) const site = fpd.site || {} site.page = bidderRequest.refererInfo.referer // clever trick to get the domain - site.domain = utils.parseUrl(site.page).hostname + site.domain = parseUrl(site.page).hostname const sovrnBidReq = { - id: utils.getUniqueIdentifierStr(), + id: getUniqueIdentifierStr(), imp: sovrnImps, site: site, user: fpd.user || {} @@ -106,18 +144,18 @@ export const spec = { } if (bidderRequest.gdprConsent) { - utils.deepSetValue(sovrnBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); - utils.deepSetValue(sovrnBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString) + deepSetValue(sovrnBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + deepSetValue(sovrnBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString) } if (bidderRequest.uspConsent) { - utils.deepSetValue(sovrnBidReq, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(sovrnBidReq, 'regs.ext.us_privacy', bidderRequest.uspConsent); } if (eids) { - utils.deepSetValue(sovrnBidReq, 'user.ext.eids', eids) - utils.deepSetValue(sovrnBidReq, 'user.ext.tpid', tpid) + deepSetValue(sovrnBidReq, 'user.ext.eids', eids) + deepSetValue(sovrnBidReq, 'user.ext.tpid', tpid) if (criteoId) { - utils.deepSetValue(sovrnBidReq, 'user.ext.prebid_criteoid', criteoId) + deepSetValue(sovrnBidReq, 'user.ext.prebid_criteoid', criteoId) } } @@ -131,7 +169,7 @@ export const spec = { options: {contentType: 'text/plain'} } } catch (e) { - utils.logError('Could not build bidrequest, error deatils:', e); + logError('Could not build bidrequest, error deatils:', e); } }, @@ -149,7 +187,7 @@ export const spec = { seatbid[0].bid && seatbid[0].bid.length > 0) { seatbid[0].bid.map(sovrnBid => { - sovrnBidResponses.push({ + const bid = { requestId: sovrnBid.impid, cpm: parseFloat(sovrnBid.price), width: parseInt(sovrnBid.w), @@ -158,15 +196,23 @@ export const spec = { dealId: sovrnBid.dealid || null, currency: 'USD', netRevenue: true, - mediaType: BANNER, - ad: decodeURIComponent(`${sovrnBid.adm}`), - ttl: sovrnBid.ext ? (sovrnBid.ext.ttl || 90) : 90 - }); + ttl: sovrnBid.ext ? (sovrnBid.ext.ttl || 90) : 90, + meta: { advertiserDomains: sovrnBid && sovrnBid.adomain ? sovrnBid.adomain : [] } + } + + if (!sovrnBid.nurl) { + bid.mediaType = VIDEO + bid.vastXml = decodeURIComponent(sovrnBid.adm) + } else { + bid.mediaType = BANNER + bid.ad = decodeURIComponent(`${sovrnBid.adm}`) + } + sovrnBidResponses.push(bid); }); } return sovrnBidResponses } catch (e) { - utils.logError('Could not intrepret bidresponse, error deatils:', e); + logError('Could not intrepret bidresponse, error deatils:', e); } }, @@ -175,7 +221,7 @@ export const spec = { const tracks = [] if (serverResponses && serverResponses.length !== 0) { if (syncOptions.iframeEnabled) { - const iidArr = serverResponses.filter(resp => utils.deepAccess(resp, 'body.ext.iid')) + const iidArr = serverResponses.filter(resp => deepAccess(resp, 'body.ext.iid')) .map(resp => resp.body.ext.iid); const params = []; if (gdprConsent && gdprConsent.gdprApplies && typeof gdprConsent.consentString === 'string') { @@ -195,7 +241,7 @@ export const spec = { } if (syncOptions.pixelEnabled) { - serverResponses.filter(resp => utils.deepAccess(resp, 'body.ext.sync.pixels')) + serverResponses.filter(resp => deepAccess(resp, 'body.ext.sync.pixels')) .reduce((acc, resp) => acc.concat(resp.body.ext.sync.pixels), []) .map(pixel => pixel.url) .forEach(url => tracks.push({ type: 'image', url })) @@ -208,4 +254,34 @@ export const spec = { }, } -registerBidder(spec); +function _buildVideoRequestObj(bid) { + const videoObj = {} + const videoAdUnitParams = deepAccess(bid, 'mediaTypes.video', {}) + const videoBidderParams = deepAccess(bid, 'params.video', {}) + const computedParams = {} + + if (Array.isArray(videoAdUnitParams.playerSize)) { + const sizes = (Array.isArray(videoAdUnitParams.playerSize[0])) ? videoAdUnitParams.playerSize[0] : videoAdUnitParams.playerSize + computedParams.w = sizes[0] + computedParams.h = sizes[1] + } + + const videoParams = { + ...computedParams, + ...videoAdUnitParams, + ...videoBidderParams + }; + + Object.keys(ORTB_VIDEO_PARAMS).forEach(paramName => { + if (videoParams.hasOwnProperty(paramName)) { + if (ORTB_VIDEO_PARAMS[paramName](videoParams[paramName])) { + videoObj[paramName] = videoParams[paramName] + } else { + logWarn(`The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`); + } + } + }) + return videoObj +} + +registerBidder(spec) diff --git a/modules/sovrnBidAdapter.md b/modules/sovrnBidAdapter.md index 2b5d21d5515..53e3158024d 100644 --- a/modules/sovrnBidAdapter.md +++ b/modules/sovrnBidAdapter.md @@ -10,9 +10,9 @@ Maintainer: jrosendahl@sovrn.com Sovrn's adapter integration to the Prebid library. Posts plain-text JSON to the /rtb/bid endpoint. -# Test Parameters +# Banner Test Parameters -``` +```js var adUnits = [ { code: 'test-leaderboard', @@ -45,3 +45,81 @@ var adUnits = [ } ] ``` + +# Video Test Parameters +### Instream +```js +var videoAdUnit = { + code: 'video1', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + }, + }, + bids: [ + { + bidder: 'sovrn', + // Prebid Server Bidder Params https://docs.prebid.org/dev-docs/pbs-bidders.html#sovrn + params: { + tagid: '315045', + bidfloor: '0.04', + }, + }, + ], +} +``` +### Outstream +```js +var adUnits = [ + { + code: videoId, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 360], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + }, + }, + bids: [ + { + bidder: 'sovrn', + // Prebid Server Bidder Params https://docs.prebid.org/dev-docs/pbs-bidders.html#sovrn + params: { + tagid: '315045', + bidfloor: '0.04', + }, + }, + ], + renderer: { + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: function (bid) { + adResponse = { + ad: { + video: { + content: bid.vastXml, + player_height: bid.height, + player_width: bid.width, + }, + }, + } + // push to render queue because ANOutstreamVideo may not be loaded yet. + bid.renderer.push(() => { + ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: adResponse, + }) + }) + }, + }, + }, +] +``` diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index b60d25db4d6..7d5865684a7 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { logError, deepAccess, isArray, getBidIdParameter, getDNT, deepSetValue, isEmpty, _each, logMessage, logWarn, isBoolean, isNumber, isPlainObject, isFn } from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -24,34 +24,34 @@ export const spec = { */ isBidRequestValid: function(bid) { if (bid && typeof bid.params !== 'object') { - utils.logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); + logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); return false; } - if (!utils.deepAccess(bid, 'mediaTypes.video')) { - utils.logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); + if (!deepAccess(bid, 'mediaTypes.video')) { + logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); return false; } - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - if (!playerSize || !utils.isArray(playerSize)) { - utils.logError(BIDDER_CODE + ': mediaTypes.video.playerSize is not defined in the bidder settings.'); + const playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); + if (!playerSize || !isArray(playerSize)) { + logError(BIDDER_CODE + ': mediaTypes.video.playerSize is not defined in the bidder settings.'); return false; } - if (!utils.getBidIdParameter('channel_id', bid.params)) { - utils.logError(BIDDER_CODE + ': channel_id is not present in bidder params'); + if (!getBidIdParameter('channel_id', bid.params)) { + logError(BIDDER_CODE + ': channel_id is not present in bidder params'); return false; } - if (utils.deepAccess(bid, 'mediaTypes.video.context') == 'outstream' || utils.deepAccess(bid, 'params.ad_unit') == 'outstream') { - if (!utils.getBidIdParameter('outstream_function', bid.params)) { - if (!utils.getBidIdParameter('outstream_options', bid.params)) { - utils.logError(BIDDER_CODE + ': please define outstream_options parameter or override the default SpotX outstream rendering by defining your own Outstream function using field outstream_function.'); + if (deepAccess(bid, 'mediaTypes.video.context') == 'outstream' || deepAccess(bid, 'params.ad_unit') == 'outstream') { + if (!getBidIdParameter('outstream_function', bid.params)) { + if (!getBidIdParameter('outstream_options', bid.params)) { + logError(BIDDER_CODE + ': please define outstream_options parameter or override the default SpotX outstream rendering by defining your own Outstream function using field outstream_function.'); return false; } - if (!utils.getBidIdParameter('slot', bid.params.outstream_options)) { - utils.logError(BIDDER_CODE + ': please define parameter slot in outstream_options object in the configuration.'); + if (!getBidIdParameter('slot', bid.params.outstream_options)) { + logError(BIDDER_CODE + ': please define parameter slot in outstream_options object in the configuration.'); return false; } } @@ -75,54 +75,54 @@ export const spec = { const siteId = ''; const spotxRequests = bidRequests.map(function(bid) { let page; - if (utils.getBidIdParameter('page', bid.params)) { - page = utils.getBidIdParameter('page', bid.params); + if (getBidIdParameter('page', bid.params)) { + page = getBidIdParameter('page', bid.params); } else if (config.getConfig('pageUrl')) { page = config.getConfig('pageUrl'); } else { page = referer; } - const channelId = utils.getBidIdParameter('channel_id', bid.params); + const channelId = getBidIdParameter('channel_id', bid.params); let pubcid = null; - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); const contentWidth = playerSize[0][0]; const contentHeight = playerSize[0][1]; - const secure = isPageSecure || (utils.getBidIdParameter('secure', bid.params) ? 1 : 0); + const secure = isPageSecure || (getBidIdParameter('secure', bid.params) ? 1 : 0); const ext = { sdk_name: 'Prebid 1+', versionOrtb: ORTB_VERSION }; - if (utils.getBidIdParameter('hide_skin', bid.params) != '') { - ext.hide_skin = +!!utils.getBidIdParameter('hide_skin', bid.params); + if (getBidIdParameter('hide_skin', bid.params) != '') { + ext.hide_skin = +!!getBidIdParameter('hide_skin', bid.params); } - if (utils.getBidIdParameter('ad_volume', bid.params) != '') { - ext.ad_volume = utils.getBidIdParameter('ad_volume', bid.params); + if (getBidIdParameter('ad_volume', bid.params) != '') { + ext.ad_volume = getBidIdParameter('ad_volume', bid.params); } - if (utils.getBidIdParameter('ad_unit', bid.params) != '') { - ext.ad_unit = utils.getBidIdParameter('ad_unit', bid.params); + if (getBidIdParameter('ad_unit', bid.params) != '') { + ext.ad_unit = getBidIdParameter('ad_unit', bid.params); } - if (utils.getBidIdParameter('outstream_options', bid.params) != '') { - ext.outstream_options = utils.getBidIdParameter('outstream_options', bid.params); + if (getBidIdParameter('outstream_options', bid.params) != '') { + ext.outstream_options = getBidIdParameter('outstream_options', bid.params); } - if (utils.getBidIdParameter('outstream_function', bid.params) != '') { - ext.outstream_function = utils.getBidIdParameter('outstream_function', bid.params); + if (getBidIdParameter('outstream_function', bid.params) != '') { + ext.outstream_function = getBidIdParameter('outstream_function', bid.params); } - if (utils.getBidIdParameter('custom', bid.params) != '') { - ext.custom = utils.getBidIdParameter('custom', bid.params); + if (getBidIdParameter('custom', bid.params) != '') { + ext.custom = getBidIdParameter('custom', bid.params); } - if (utils.getBidIdParameter('pre_market_bids', bid.params) != '' && utils.isArray(utils.getBidIdParameter('pre_market_bids', bid.params))) { - const preMarketBids = utils.getBidIdParameter('pre_market_bids', bid.params); + if (getBidIdParameter('pre_market_bids', bid.params) != '' && isArray(getBidIdParameter('pre_market_bids', bid.params))) { + const preMarketBids = getBidIdParameter('pre_market_bids', bid.params); ext.pre_market_bids = []; for (let i in preMarketBids) { const preMarketBid = preMarketBids[i]; @@ -150,7 +150,7 @@ export const spec = { } } - const mimes = utils.getBidIdParameter('mimes', bid.params) || ['application/javascript', 'video/mp4', 'video/webm']; + const mimes = getBidIdParameter('mimes', bid.params) || ['application/javascript', 'video/mp4', 'video/webm']; const spotxReq = { id: bid.bidId, @@ -163,28 +163,38 @@ export const spec = { } }; - if (utils.getBidIdParameter('price_floor', bid.params) != '') { - spotxReq.bidfloor = utils.getBidIdParameter('price_floor', bid.params); + if (isFn(bid.getFloor)) { + let floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: 'video', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + spotxReq.bidfloor = floorInfo.floor; + } + } else if (getBidIdParameter('price_floor', bid.params) != '') { + spotxReq.bidfloor = getBidIdParameter('price_floor', bid.params); } - if (utils.getBidIdParameter('start_delay', bid.params) != '') { - spotxReq.video.startdelay = 0 + Boolean(utils.getBidIdParameter('start_delay', bid.params)); + if (getBidIdParameter('start_delay', bid.params) != '') { + spotxReq.video.startdelay = 0 + Boolean(getBidIdParameter('start_delay', bid.params)); } - if (utils.getBidIdParameter('min_duration', bid.params) != '') { - spotxReq.video.minduration = utils.getBidIdParameter('min_duration', bid.params); + if (getBidIdParameter('min_duration', bid.params) != '') { + spotxReq.video.minduration = getBidIdParameter('min_duration', bid.params); } - if (utils.getBidIdParameter('max_duration', bid.params) != '') { - spotxReq.video.maxduration = utils.getBidIdParameter('max_duration', bid.params); + if (getBidIdParameter('max_duration', bid.params) != '') { + spotxReq.video.maxduration = getBidIdParameter('max_duration', bid.params); } - if (utils.getBidIdParameter('placement_type', bid.params) != '') { - spotxReq.video.ext.placement = utils.getBidIdParameter('placement_type', bid.params); + if (getBidIdParameter('placement_type', bid.params) != '') { + spotxReq.video.ext.placement = getBidIdParameter('placement_type', bid.params); } - if (utils.getBidIdParameter('position', bid.params) != '') { - spotxReq.video.ext.pos = utils.getBidIdParameter('position', bid.params); + if (getBidIdParameter('position', bid.params) != '') { + spotxReq.video.ext.pos = getBidIdParameter('position', bid.params); } if (bid.crumbs && bid.crumbs.pubcid) { @@ -195,7 +205,7 @@ export const spec = { const device = { h: screen.height, w: screen.width, - dnt: utils.getDNT() ? 1 : 0, + dnt: getDNT() ? 1 : 0, language: navigator[language].split('-')[0], make: navigator.vendor ? navigator.vendor : '', ua: navigator.userAgent @@ -221,13 +231,13 @@ export const spec = { requestPayload['ext']['wrap_response'] = 0; } - if (utils.getBidIdParameter('number_of_ads', bid.params)) { - requestPayload['ext']['number_of_ads'] = utils.getBidIdParameter('number_of_ads', bid.params); + if (getBidIdParameter('number_of_ads', bid.params)) { + requestPayload['ext']['number_of_ads'] = getBidIdParameter('number_of_ads', bid.params); } const userExt = {}; - if (utils.getBidIdParameter('spotx_all_google_consent', bid.params) == 1) { + if (getBidIdParameter('spotx_all_google_consent', bid.params) == 1) { userExt['consented_providers_settings'] = GOOGLE_CONSENT; } @@ -236,16 +246,16 @@ export const spec = { userExt.consent = bidderRequest.gdprConsent.consentString; if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { - utils.deepSetValue(requestPayload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + deepSetValue(requestPayload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } } if (bidderRequest && bidderRequest.uspConsent) { - utils.deepSetValue(requestPayload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(requestPayload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } // ID5 fied - if (utils.deepAccess(bid, 'userId.id5id.uid')) { + if (deepAccess(bid, 'userId.id5id.uid')) { userExt.eids = userExt.eids || []; userExt.eids.push( { @@ -288,7 +298,7 @@ export const spec = { } // Only add the user object if it's not empty - if (!utils.isEmpty(userExt)) { + if (!isEmpty(userExt)) { requestPayload.user = { ext: userExt }; } const urlQueryParams = 'src_sys=prebid' @@ -313,9 +323,9 @@ export const spec = { const bidResponses = []; const serverResponseBody = serverResponse.body; - if (serverResponseBody && utils.isArray(serverResponseBody.seatbid)) { - utils._each(serverResponseBody.seatbid, function(bids) { - utils._each(bids.bid, function(spotxBid) { + if (serverResponseBody && isArray(serverResponseBody.seatbid)) { + _each(serverResponseBody.seatbid, function(bids) { + _each(bids.bid, function(spotxBid) { let currentBidRequest = {}; for (let i in bidderRequest.bidRequest.bids) { if (spotxBid.impid == bidderRequest.bidRequest.bids[i].bidId) { @@ -327,7 +337,7 @@ export const spec = { * Make sure currency and price are the right ones * TODO: what about the pre_market_bid partners sizes? */ - utils._each(currentBidRequest.params.pre_market_bids, function(pmb) { + _each(currentBidRequest.params.pre_market_bids, function(pmb) { if (pmb.deal_id == spotxBid.id) { spotxBid.price = pmb.price; serverResponseBody.cur = pmb.currency; @@ -361,10 +371,10 @@ export const spec = { bid.meta.advertiserDomains = spotxBid.adomain; } - const context1 = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context'); - const context2 = utils.deepAccess(currentBidRequest, 'params.ad_unit'); + const context1 = deepAccess(currentBidRequest, 'mediaTypes.video.context'); + const context2 = deepAccess(currentBidRequest, 'params.ad_unit'); if (context1 == 'outstream' || context2 == 'outstream') { - const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); + const playersize = deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, url: '/', @@ -372,11 +382,11 @@ export const spec = { adText: 'SpotX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], player_height: playersize[0][1], - content_page_url: utils.deepAccess(bidderRequest, 'data.site.page'), - ad_mute: +!!utils.deepAccess(currentBidRequest, 'params.ad_mute'), - hide_skin: +!!utils.deepAccess(currentBidRequest, 'params.hide_skin'), - outstream_options: utils.deepAccess(currentBidRequest, 'params.outstream_options'), - outstream_function: utils.deepAccess(currentBidRequest, 'params.outstream_function') + content_page_url: deepAccess(bidderRequest, 'data.site.page'), + ad_mute: +!!deepAccess(currentBidRequest, 'params.ad_mute'), + hide_skin: +!!deepAccess(currentBidRequest, 'params.hide_skin'), + outstream_options: deepAccess(currentBidRequest, 'params.outstream_options'), + outstream_function: deepAccess(currentBidRequest, 'params.outstream_function') } }); @@ -384,17 +394,17 @@ export const spec = { renderer.setRender(outstreamRender); renderer.setEventHandlers({ impression: function impression() { - return utils.logMessage('SpotX outstream video impression event'); + return logMessage('SpotX outstream video impression event'); }, loaded: function loaded() { - return utils.logMessage('SpotX outstream video loaded event'); + return logMessage('SpotX outstream video loaded event'); }, ended: function ended() { - utils.logMessage('SpotX outstream renderer video event'); + logMessage('SpotX outstream renderer video event'); } }); } catch (err) { - utils.logWarn('Prebid Error calling setRender or setEventHandlers on renderer', err); + logWarn('Prebid Error calling setRender or setEventHandlers on renderer', err); } bid.renderer = renderer; } @@ -409,8 +419,8 @@ export const spec = { } function createOutstreamScript(bid) { - const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); - utils.logMessage('[SPOTX][renderer] Handle SpotX outstream renderer'); + const slot = getBidIdParameter('slot', bid.renderer.config.outstream_options); + logMessage('[SPOTX][renderer] Handle SpotX outstream renderer'); const script = window.document.createElement('script'); script.type = 'text/javascript'; script.src = 'https://js.spotx.tv/easi/v1/' + bid.channel_id + '.js'; @@ -420,8 +430,8 @@ function createOutstreamScript(bid) { dataSpotXParams['data-spotx_content_page_url'] = bid.renderer.config.content_page_url; dataSpotXParams['data-spotx_ad_unit'] = 'incontent'; - utils.logMessage('[SPOTX][renderer] Default behavior'); - if (utils.getBidIdParameter('ad_mute', bid.renderer.config.outstream_options)) { + logMessage('[SPOTX][renderer] Default behavior'); + if (getBidIdParameter('ad_mute', bid.renderer.config.outstream_options)) { dataSpotXParams['data-spotx_ad_mute'] = '1'; } dataSpotXParams['data-spotx_collapse'] = '0'; @@ -429,9 +439,9 @@ function createOutstreamScript(bid) { dataSpotXParams['data-spotx_blocked_autoplay_override_mode'] = '1'; dataSpotXParams['data-spotx_video_slot_can_autoplay'] = '1'; - const playersizeAutoAdapt = utils.getBidIdParameter('playersize_auto_adapt', bid.renderer.config.outstream_options); - if (playersizeAutoAdapt && utils.isBoolean(playersizeAutoAdapt) && playersizeAutoAdapt === true) { - const ratio = bid.width && utils.isNumber(bid.width) && bid.height && utils.isNumber(bid.height) ? bid.width / bid.height : 4 / 3; + const playersizeAutoAdapt = getBidIdParameter('playersize_auto_adapt', bid.renderer.config.outstream_options); + if (playersizeAutoAdapt && isBoolean(playersizeAutoAdapt) && playersizeAutoAdapt === true) { + const ratio = bid.width && isNumber(bid.width) && bid.height && isNumber(bid.height) ? bid.width / bid.height : 4 / 3; const slotClientWidth = window.document.getElementById(slot).clientWidth; let playerWidth = bid.renderer.config.player_width; let playerHeight = bid.renderer.config.player_height; @@ -453,13 +463,13 @@ function createOutstreamScript(bid) { dataSpotXParams['data-spotx_content_height'] = '' + contentHeight; } - const customOverride = utils.getBidIdParameter('custom_override', bid.renderer.config.outstream_options); - if (customOverride && utils.isPlainObject(customOverride)) { - utils.logMessage('[SPOTX][renderer] Custom behavior.'); + const customOverride = getBidIdParameter('custom_override', bid.renderer.config.outstream_options); + if (customOverride && isPlainObject(customOverride)) { + logMessage('[SPOTX][renderer] Custom behavior.'); for (let name in customOverride) { if (customOverride.hasOwnProperty(name)) { if (name === 'channel_id' || name === 'vast_url' || name === 'content_page_url' || name === 'ad_unit') { - utils.logWarn('[SPOTX][renderer] Custom behavior: following option cannot be overridden: ' + name); + logWarn('[SPOTX][renderer] Custom behavior: following option cannot be overridden: ' + name); } else { dataSpotXParams['data-spotx_' + name] = customOverride[name]; } @@ -482,7 +492,7 @@ function outstreamRender(bid) { bid.renderer.config.outstream_function(bid, script); } else { try { - const inIframe = utils.getBidIdParameter('in_iframe', bid.renderer.config.outstream_options); + const inIframe = getBidIdParameter('in_iframe', bid.renderer.config.outstream_options); if (inIframe && window.document.getElementById(inIframe).nodeName == 'IFRAME') { const rawframe = window.document.getElementById(inIframe); let framedoc = rawframe.contentDocument; @@ -491,7 +501,7 @@ function outstreamRender(bid) { } framedoc.body.appendChild(script); } else { - const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); + const slot = getBidIdParameter('slot', bid.renderer.config.outstream_options); if (slot && window.document.getElementById(slot)) { window.document.getElementById(slot).appendChild(script); } else { @@ -499,7 +509,7 @@ function outstreamRender(bid) { } } } catch (err) { - utils.logError('[SPOTX][renderer] Error:' + err.message) + logError('[SPOTX][renderer] Error:' + err.message) } } } diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index 391f3a05721..02b786f4fd2 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -1,18 +1,23 @@ -import * as utils from '../src/utils.js'; +import { isArray, deepAccess, logWarn, parseUrl } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import strIncludes from 'core-js-pure/features/string/includes.js'; const BIDDER_CODE = 'sspBC'; const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync'; const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify'; +const TRACKER_URL = 'https://bdr.wpcdn.pl/tag/jstracker.js'; +const GVLID = 676; const TMAX = 450; -const BIDDER_VERSION = '4.8'; +const BIDDER_VERSION = '5.41'; const W = window; const { navigator } = W; const oneCodeDetection = {}; +const adUnitsCalled = {}; +const adSizesCalled = {}; +const pageView = {}; var consentApiVersion; /** @@ -21,16 +26,16 @@ var consentApiVersion; */ const getNotificationPayload = bidData => { if (bidData) { - const bids = utils.isArray(bidData) ? bidData : [bidData]; + const bids = isArray(bidData) ? bidData : [bidData]; if (bids.length > 0) { const result = { requestId: undefined, siteId: [], - adUnit: [], slotId: [], + tagid: [], } bids.forEach(bid => { - let params = utils.isArray(bid.params) ? bid.params[0] : bid.params; + let params = isArray(bid.params) ? bid.params[0] : bid.params; params = params || {}; // check for stored detection @@ -52,7 +57,7 @@ const getNotificationPayload = bidData => { result.adomain = meta.advertiserDomains && meta.advertiserDomains[0]; result.networkName = meta.networkName; } - result.adUnit.push(bid.adUnitCode) + result.tagid.push(bid.adUnitCode); result.requestId = bid.auctionId || result.requestId; result.timeout = bid.timeout || result.timeout; }) @@ -69,7 +74,8 @@ const cookieSupport = () => { }; const applyClientHints = ortbRequest => { - const connection = navigator.connection || false; + const { location } = document; + const { connection = {}, deviceMemory, userAgentData = {} } = navigator; const viewport = W.visualViewport || false; const segments = []; const hints = { @@ -77,11 +83,23 @@ const applyClientHints = ortbRequest => { 'CH-Rtt': connection.rtt, 'CH-SaveData': connection.saveData, 'CH-Downlink': connection.downlink, - 'CH-DeviceMemory': navigator.deviceMemory, + 'CH-DeviceMemory': deviceMemory, 'CH-Dpr': W.devicePixelRatio, 'CH-ViewportWidth': viewport.width, + 'CH-BrowserBrands': JSON.stringify(userAgentData.brands), + 'CH-isMobile': userAgentData.mobile, }; + /** + Check / generate page view id + Should be generated dureing first call to applyClientHints(), + and re-generated if pathname has changed + */ + if (!pageView.id || location.pathname !== pageView.path) { + pageView.path = location.pathname; + pageView.id = Math.floor(1E20 * Math.random()); + } + Object.keys(hints).forEach(key => { const hint = hints[key]; @@ -97,31 +115,53 @@ const applyClientHints = ortbRequest => { id: '12', name: 'NetInfo', segment: segments, + }, { + id: '7', + name: 'pvid', + segment: [ + { + value: `${pageView.id}` + } + ] }]; - ortbRequest.user = Object.assign(ortbRequest.user, { data }); + const ch = { data }; + ortbRequest.user = { ...ortbRequest.user, ...ch }; }; -function applyGdpr(bidderRequest, ortbRequest) { - if (bidderRequest && bidderRequest.gdprConsent) { - consentApiVersion = bidderRequest.gdprConsent.apiVersion; - ortbRequest.regs = Object.assign(ortbRequest.regs, { '[ortb_extensions.gdpr]': bidderRequest.gdprConsent.gdprApplies ? 1 : 0 }); - ortbRequest.user = Object.assign(ortbRequest.user, { '[ortb_extensions.consent]': bidderRequest.gdprConsent.consentString }); +const applyUserIds = (validBidRequest, ortbRequest) => { + const eids = validBidRequest.userIdAsEids + if (eids && eids.length) { + const ids = { eids }; + ortbRequest.user = { ...ortbRequest.user, ...ids }; } -} - -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = utils.deepAccess(collection[i], key); +}; - if (result) { - return result; - } +/** + * Add GDPR data to oRTB request + * Store conset API version (will be required by user sync) + */ +const applyGdpr = (bidderRequest, ortbRequest) => { + const { gdprConsent } = bidderRequest; + if (gdprConsent) { + const { apiVersion, gdprApplies, consentString } = gdprConsent; + consentApiVersion = apiVersion; + ortbRequest.regs = Object.assign(ortbRequest.regs, { 'gdpr': gdprApplies ? 1 : 0 }); + ortbRequest.user = Object.assign(ortbRequest.user, { 'consent': consentString }); } } -function sendNotification(payload) { +/** + * Get value for first occurence of key within the collection + */ +const setOnAny = (collection, key) => collection.reduce((prev, next) => prev || deepAccess(next, key), false); + +/** + * Send payload to notification endpoint + */ +const sendNotification = payload => { ajax(NOTIFY_URL, null, JSON.stringify(payload), { + contentType: 'application/json', withCredentials: false, method: 'POST', crossOrigin: true @@ -132,17 +172,15 @@ function sendNotification(payload) { * @param {object} slot Ad Unit Params by Prebid * @returns {object} Banner by OpenRTB 2.5 §3.2.6 */ -function mapBanner(slot) { +const mapBanner = slot => { if (slot.mediaType === 'banner' || - utils.deepAccess(slot, 'mediaTypes.banner') || + deepAccess(slot, 'mediaTypes.banner') || (!slot.mediaType && !slot.mediaTypes)) { const format = slot.sizes.map(size => ({ w: size[0], h: size[1], })); - // override - tylko 1szy wymiar - // format = format.slice(0, 1); return { format, id: slot.bidId, @@ -150,26 +188,222 @@ function mapBanner(slot) { } } -function mapImpression(slot) { - const { adUnitCode, bidId, params } = slot; - const { id, siteId } = params || {}; +/** + * @param {string} paramName Native parameter name + * @param {object} paramValue Native parameter value + * @returns {object} native asset object that conforms to ortb native ads spec + */ +const mapAsset = (paramName, paramValue) => { + let asset; + switch (paramName) { + case 'title': + asset = { + id: 0, + required: paramValue.required, + title: { len: paramValue.len } + } + break; + case 'cta': + asset = { + id: 1, + required: paramValue.required, + data: { type: 12 } + } + break; + case 'icon': + asset = { + id: 2, + required: paramValue.required, + img: { type: 1, w: paramValue.sizes[0], h: paramValue.sizes[1] } + } + break; + case 'image': + asset = { + id: 3, + required: paramValue.required, + img: { type: 3, w: paramValue.sizes[0], h: paramValue.sizes[1] } + } + break; + case 'body': + asset = { + id: 4, + required: paramValue.required, + data: { type: 2 } + } + break; + case 'sponsoredBy': + asset = { + id: 5, + required: paramValue.required, + data: { type: 1 } + } + break; + } + return asset; +} + +/** + * @param {object} slot Ad Unit Params by Prebid + * @returns {object} native object that conforms to ortb native ads spec + */ +const mapNative = slot => { + const native = deepAccess(slot, 'mediaTypes.native'); + let assets; + if (native) { + const nativeParams = Object.keys(native); + assets = []; + nativeParams.forEach(par => { + const newAsset = mapAsset(par, native[par]); + if (newAsset) { assets.push(newAsset) }; + }); + } + return assets ? { request: JSON.stringify({ native: { assets } }) } : undefined; +} + +var mapVideo = slot => { + var video = deepAccess(slot, 'mediaTypes.video'); + var videoParamsUsed = ['api', 'context', 'linearity', 'maxduration', 'mimes', 'protocols']; + var videoAssets; + + if (video) { + var videoParams = Object.keys(video); + var playerSize = video.playerSize; + videoAssets = {}; // player width / height + + if (playerSize) { + var maxSize = playerSize.reduce(function (prev, next) { + return next[0] >= prev[0] && next[1] >= prev[1] ? next : prev; + }, [1, 1]); + videoAssets.w = maxSize[0]; + videoAssets.h = maxSize[1]; + } // remaining supported params + + videoParams.forEach(function (par) { + if (videoParamsUsed.indexOf(par) >= 0) { + videoAssets[par] = video[par]; + } + + ; + }); + } + + return videoAssets; +}; + +const mapImpression = slot => { + const { adUnitCode, bidId, params = {}, ortb2Imp = {} } = slot; + const { id, siteId } = params; + const { ext = {} } = ortb2Imp; + + /* + check max size for this imp, and check/store number this size was called (for current view) + send this info as ext.pbsize + */ + const slotSize = slot.sizes.length ? slot.sizes.reduce((prev, next) => prev[0] * prev[1] <= next[0] * next[1] ? next : prev).join('x') : '1x1'; + + if (!adUnitsCalled[adUnitCode]) { + // this is a new adunit - assign & save pbsize + adSizesCalled[slotSize] = adSizesCalled[slotSize] ? adSizesCalled[slotSize] += 1 : 1; + adUnitsCalled[adUnitCode] = `${slotSize}_${adSizesCalled[slotSize]}` + } + + ext.data = Object.assign({ pbsize: adUnitsCalled[adUnitCode] }, ext.data); + const imp = { - id: id && siteId ? id : 'bidid-' + bidId, + id: id && siteId ? id.padStart(3, '0') : 'bidid-' + bidId, banner: mapBanner(slot), - /* native: mapNative(slot), */ + native: mapNative(slot), + video: mapVideo(slot), tagid: adUnitCode, + ext, }; - const bidfloor = (slot.params && slot.params.bidFloor) ? parseFloat(slot.params.bidFloor) : undefined; + // Check floorprices for this imp + if (typeof slot.getFloor === 'function') { + var bannerFloor = 0; + var nativeFloor = 0; + var videoFloor = 0; // sspBC adapter accepts only floor per imp - check for maximum value for requested ad types and sizes + + if (slot.sizes.length) { + bannerFloor = slot.sizes.reduce(function (prev, next) { + var currentFloor = slot.getFloor({ + mediaType: 'banner', + size: next + }).floor; + return prev > currentFloor ? prev : currentFloor; + }, 0); + } - if (bidfloor) { - imp.bidfloor = bidfloor; + nativeFloor = slot.getFloor({ + mediaType: 'native' + }); + videoFloor = slot.getFloor({ + mediaType: 'video' + }); + imp.bidfloor = Math.max(bannerFloor, nativeFloor, videoFloor); } - return imp; } -function renderCreative(site, auctionId, bid, seat, request) { +const isVideoAd = bid => { + const xmlTester = new RegExp(/^<\?xml/); + return bid.adm && bid.adm.match(xmlTester); +} + +const isNativeAd = bid => { + const xmlTester = new RegExp(/^{['"]native['"]/); + + return bid.admNative || (bid.adm && bid.adm.match(xmlTester)); +} + +const parseNative = nativeData => { + const result = {}; + nativeData.assets.forEach(asset => { + const id = parseInt(asset.id); + switch (id) { + case 0: + result.title = asset.title.text; + break; + case 1: + result.cta = asset.data.value; + break; + case 2: + result.icon = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h, + }; + break; + case 3: + result.image = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h, + }; + break; + case 4: + result.body = asset.data.value; + break; + case 5: + result.sponsoredBy = asset.data.value; + break; + + default: + logWarn('Unrecognized native asset', asset); + } + }); + result.clickUrl = nativeData.link.url; + result.impressionTrackers = nativeData.imptrackers; + + if (isArray(nativeData.jstracker)) { + result.javascriptTrackers = nativeData.jstracker; + } else if (nativeData.jstracker) { + result.javascriptTrackers = [nativeData.jstracker]; + } + return result; +} + +const renderCreative = (site, auctionId, bid, seat, request) => { let gam; const mcad = { @@ -203,7 +437,7 @@ function renderCreative(site, auctionId, bid, seat, request) { gam.targeting = {}; } } catch (err) { - utils.logWarn('Could not parse adm data', bid.adm); + logWarn('Could not parse adm data', bid.adm); } } @@ -221,17 +455,16 @@ function renderCreative(site, auctionId, bid, seat, request) { @@ -245,8 +478,9 @@ function renderCreative(site, auctionId, bid, seat, request) { const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: [], - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, NATIVE, VIDEO], isBidRequestValid(bid) { // as per OneCode integration, bids without params are valid return true; @@ -259,7 +493,7 @@ const spec = { const siteId = setOnAny(validBidRequests, 'params.siteId'); const publisherId = setOnAny(validBidRequests, 'params.publisherId'); const page = setOnAny(validBidRequests, 'params.page') || bidderRequest.refererInfo.referer; - const domain = setOnAny(validBidRequests, 'params.domain') || utils.parseUrl(page).hostname; + const domain = setOnAny(validBidRequests, 'params.domain') || parseUrl(page).hostname; const tmax = setOnAny(validBidRequests, 'params.tmax') ? parseInt(setOnAny(validBidRequests, 'params.tmax'), 10) : TMAX; const pbver = '$prebid.version$'; const testMode = setOnAny(validBidRequests, 'params.test') ? 1 : undefined; @@ -289,10 +523,11 @@ const spec = { applyGdpr(bidderRequest, payload); applyClientHints(payload); + applyUserIds(validBidRequests[0], payload); return { method: 'POST', - url: BIDDER_URL + '?cs=' + cookieSupport() + '&bdver=' + BIDDER_VERSION + '&pbver=' + pbver + '&inver=0', + url: `${BIDDER_URL}?cs=${cookieSupport()}&bdver=${BIDDER_VERSION}&pbver=${pbver}&inver=0`, data: JSON.stringify(payload), bidderRequest, }; @@ -315,11 +550,11 @@ const spec = { seat = seatbid.seat; seatbid.bid.forEach(serverBid => { // get data from bid response - const { adomain, crid, impid, exp, ext, price, w, h } = serverBid; + const { adomain, crid = `mcad_${bidderRequest.auctionId}_${site.slot}`, impid, exp = 300, ext, price, w, h } = serverBid; const bidRequest = bidderRequest.bids.filter(b => { - const { bidId, params } = b; - const { id, siteId } = params || {}; + const { bidId, params = {} } = b; + const { id, siteId } = params; const currentBidId = id && siteId ? id : 'bidid-' + bidId; return currentBidId === impid; })[0]; @@ -334,10 +569,14 @@ const spec = { /* bid response might include ext object containing siteId / slotId, as detected by OneCode update site / slot data in this case + + ext also might contain publisherId and custom ad label */ - const { siteid, slotid } = ext; + const { siteid, slotid, pubid, adlabel } = ext; site.id = siteid || site.id; site.slot = slotid || site.slot; + site.publisherId = pubid; + site.adLabel = adlabel; } if (bidRequest && site.id && !strIncludes(site.id, 'bidid')) { @@ -348,34 +587,69 @@ const spec = { const bid = { requestId: bidId, - creativeId: crid || 'mcad_' + bidderRequest.auctionId + '_' + site.slot, + creativeId: crid, cpm: price, currency: response.cur, - ttl: exp || 300, + ttl: exp, width: w, height: h, bidderCode: BIDDER_CODE, - mediaType: 'banner', meta: { advertiserDomains: adomain, networkName: seat, }, netRevenue: true, - ad: renderCreative(site, response.id, serverBid, seat, bidderRequest), }; - if (bid.cpm > 0) { - // check bidFloor (if present in params) - const { bidFloor } = params || {}; - - if (!bidFloor || bid.cpm >= bidFloor) { - bids.push(bid); - } else { - utils.logWarn('Discarding bid due to bidFloor setting', bid.cpm, bidFloor); + // mediaType and ad data for instream / native / banner + if (isVideoAd(serverBid)) { + // video + bid.adType = 'instream'; + bid.mediaType = 'video'; + bid.vastXml = serverBid.adm; + bid.vastContent = serverBid.adm; + } else if (isNativeAd(serverBid)) { + // native + bid.mediaType = 'native'; + // check native object + try { + const nativeData = serverBid.admNative || JSON.parse(serverBid.adm).native; + bid.native = parseNative(nativeData); + bid.width = 1; + bid.height = 1; + + // append viewability tracker + const jsData = { + rid: bidRequest.auctionId, + crid: bid.creativeId, + adunit: bidRequest.adUnitCode, + url: bid.native.clickUrl, + vendor: seat, + site: site.id, + slot: site.slot, + cpm: bid.cpm.toPrecision(4), + } + const jsTracker = ' + + + `; +} + +registerBidder(spec); diff --git a/modules/targetVideoBidAdapter.md b/modules/targetVideoBidAdapter.md new file mode 100644 index 00000000000..557c9f94410 --- /dev/null +++ b/modules/targetVideoBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Target Video Bid Adapter +Module Type: Bidder Adapter +Maintainer: grajzer@gmail.com +``` + +# Description + +Connects to Appnexus exchange for bids. + +TargetVideo bid adapter supports Banner. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[640, 480], [300, 250]], + } + }, + bids: [{ + bidder: 'targetVideo', + params: { + placementId: 13232361 + } + }] + } +]; +``` diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 32be7e62bbd..4bea8858d98 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -1,5 +1,7 @@ +import { getValue, logError, deepAccess, getBidIdParameter, parseSizesInput, isArray } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -const utils = require('../src/utils.js'); +import {getStorageManager} from '../src/storageManager.js'; + const BIDDER_CODE = 'teads'; const GVL_ID = 132; const ENDPOINT_URL = 'https://a.teads.tv/hb/bid-request'; @@ -8,7 +10,9 @@ const gdprStatus = { GDPR_APPLIES_GLOBAL: 11, GDPR_DOESNT_APPLY: 0, CMP_NOT_FOUND_OR_ERROR: 22 -} +}; +const FP_TEADS_ID_COOKIE_NAME = '_tfpvi'; +export const storage = getStorageManager(GVL_ID, BIDDER_CODE); export const spec = { code: BIDDER_CODE, @@ -23,13 +27,13 @@ export const spec = { isBidRequestValid: function(bid) { let isValid = false; if (typeof bid.params !== 'undefined') { - let isValidPlacementId = _validateId(utils.getValue(bid.params, 'placementId')); - let isValidPageId = _validateId(utils.getValue(bid.params, 'pageId')); + let isValidPlacementId = _validateId(getValue(bid.params, 'placementId')); + let isValidPageId = _validateId(getValue(bid.params, 'pageId')); isValid = isValidPlacementId && isValidPageId; } if (!isValid) { - utils.logError('Teads placementId and pageId parameters are required. Bid aborted.'); + logError('Teads placementId and pageId parameters are required. Bid aborted.'); } return isValid; }, @@ -41,6 +45,7 @@ export const spec = { */ buildRequests: function(validBidRequests, bidderRequest) { const bids = validBidRequests.map(buildRequestObject); + const payload = { referrer: getReferrerInfo(bidderRequest), pageReferrer: document.referrer, @@ -48,7 +53,10 @@ export const spec = { timeToFirstByte: getTimeToFirstByte(window), data: bids, deviceWidth: screen.width, - hb_version: '$prebid.version$' + hb_version: '$prebid.version$', + ...getFLoCParameters(deepAccess(validBidRequests, '0.userId.flocId')), + ...getUnifiedId2Parameter(deepAccess(validBidRequests, '0.userId.uid2')), + ...getFirstPartyTeadsIdParameter() }; if (validBidRequests[0].schain) { @@ -57,11 +65,11 @@ export const spec = { let gdpr = bidderRequest.gdprConsent; if (bidderRequest && gdpr) { - let isCmp = (typeof gdpr.gdprApplies === 'boolean') - let isConsentString = (typeof gdpr.consentString === 'string') + let isCmp = typeof gdpr.gdprApplies === 'boolean'; + let isConsentString = typeof gdpr.consentString === 'string'; let status = isCmp ? findGdprStatus(gdpr.gdprApplies, gdpr.vendorData, gdpr.apiVersion) - : gdprStatus.CMP_NOT_FOUND_OR_ERROR + : gdprStatus.CMP_NOT_FOUND_OR_ERROR; payload.gdpr_iab = { consent: isConsentString ? gdpr.consentString : '', status: status, @@ -70,14 +78,14 @@ export const spec = { } if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent + payload.us_privacy = bidderRequest.uspConsent; } const payloadString = JSON.stringify(payload); return { method: 'POST', url: ENDPOINT_URL, - data: payloadString, + data: payloadString }; }, /** @@ -114,7 +122,7 @@ export const spec = { }); } return bidResponses; - }, + } }; function getReferrerInfo(bidderRequest) { @@ -159,10 +167,14 @@ function getTimeToFirstByte(win) { } function findGdprStatus(gdprApplies, gdprData, apiVersion) { - let status = gdprStatus.GDPR_APPLIES_PUBLISHER + let status = gdprStatus.GDPR_APPLIES_PUBLISHER; if (gdprApplies) { - if (isGlobalConsent(gdprData, apiVersion)) status = gdprStatus.GDPR_APPLIES_GLOBAL - } else status = gdprStatus.GDPR_DOESNT_APPLY + if (isGlobalConsent(gdprData, apiVersion)) { + status = gdprStatus.GDPR_APPLIES_GLOBAL; + } + } else { + status = gdprStatus.GDPR_DOESNT_APPLY; + } return status; } @@ -171,47 +183,51 @@ function isGlobalConsent(gdprData, apiVersion) { ? (gdprData.hasGlobalScope || gdprData.hasGlobalConsent) : gdprData && apiVersion === 2 ? !gdprData.isServiceSpecific - : false + : false; } function buildRequestObject(bid) { const reqObj = {}; - let placementId = utils.getValue(bid.params, 'placementId'); - let pageId = utils.getValue(bid.params, 'pageId'); + let placementId = getValue(bid.params, 'placementId'); + let pageId = getValue(bid.params, 'pageId'); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid'); reqObj.sizes = getSizes(bid); - reqObj.bidId = utils.getBidIdParameter('bidId', bid); - reqObj.bidderRequestId = utils.getBidIdParameter('bidderRequestId', bid); + reqObj.bidId = getBidIdParameter('bidId', bid); + reqObj.bidderRequestId = getBidIdParameter('bidderRequestId', bid); reqObj.placementId = parseInt(placementId, 10); reqObj.pageId = parseInt(pageId, 10); - reqObj.adUnitCode = utils.getBidIdParameter('adUnitCode', bid); - reqObj.auctionId = utils.getBidIdParameter('auctionId', bid); - reqObj.transactionId = utils.getBidIdParameter('transactionId', bid); + reqObj.adUnitCode = getBidIdParameter('adUnitCode', bid); + reqObj.auctionId = getBidIdParameter('auctionId', bid); + reqObj.transactionId = getBidIdParameter('transactionId', bid); + if (gpid) { reqObj.gpid = gpid; } return reqObj; } function getSizes(bid) { - return utils.parseSizesInput(concatSizes(bid)); + return parseSizesInput(concatSizes(bid)); } function concatSizes(bid) { - let playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - let videoSizes = utils.deepAccess(bid, 'mediaTypes.video.sizes'); - let bannerSizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes'); + let playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); + let videoSizes = deepAccess(bid, 'mediaTypes.video.sizes'); + let bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); - if (utils.isArray(bannerSizes) || utils.isArray(playerSize) || utils.isArray(videoSizes)) { + if (isArray(bannerSizes) || isArray(playerSize) || isArray(videoSizes)) { let mediaTypesSizes = [bannerSizes, videoSizes, playerSize]; return mediaTypesSizes .reduce(function(acc, currSize) { - if (utils.isArray(currSize)) { - if (utils.isArray(currSize[0])) { - currSize.forEach(function (childSize) { acc.push(childSize) }) + if (isArray(currSize)) { + if (isArray(currSize[0])) { + currSize.forEach(function (childSize) { + acc.push(childSize); + }) } else { acc.push(currSize); } } return acc; - }, []) + }, []); } else { return bid.sizes; } @@ -221,4 +237,39 @@ function _validateId(id) { return (parseInt(id) > 0); } +/** + * Get FLoC parameters to be sent in the bid request. + * @param `{id: string, version: string} | undefined` optionalFlocId FLoC user ID object available if "flocIdSystem" module is enabled. + * @returns `{} | {cohortId: string} | {cohortVersion: string} | {cohortId: string, cohortVersion: string}` + */ +function getFLoCParameters(optionalFlocId) { + if (!optionalFlocId) { + return {}; + } + const cohortId = optionalFlocId.id ? { cohortId: optionalFlocId.id } : {}; + const cohortVersion = optionalFlocId.version ? { cohortVersion: optionalFlocId.version } : {}; + return { ...cohortId, ...cohortVersion }; +} + +/** + * Get unified ID v2 parameter to be sent in bid request. + * @param `{id: string} | undefined` optionalUid2 uid2 user ID object available if "uid2IdSystem" module is enabled. + * @returns `{} | {unifiedId2: string}` + */ +function getUnifiedId2Parameter(optionalUid2) { + return optionalUid2 ? { unifiedId2: optionalUid2.id } : {}; +} + +/** + * Get the first-party cookie Teads ID parameter to be sent in bid request. + * @returns `{} | {firstPartyCookieTeadsId: string}` + */ +function getFirstPartyTeadsIdParameter() { + if (!storage.cookiesAreEnabled()) { + return {}; + } + const firstPartyTeadsId = storage.getCookie(FP_TEADS_ID_COOKIE_NAME); + return firstPartyTeadsId ? { firstPartyCookieTeadsId: firstPartyTeadsId } : {}; +} + registerBidder(spec); diff --git a/modules/telariaBidAdapter.js b/modules/telariaBidAdapter.js index b2904045144..50a6ccc9fb2 100644 --- a/modules/telariaBidAdapter.js +++ b/modules/telariaBidAdapter.js @@ -1,8 +1,6 @@ -import * as utils from '../src/utils.js'; -import {createBid as createBidFactory} from '../src/bidfactory.js'; +import { logError, isEmpty, deepAccess, triggerPixel, logWarn, isArray } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {VIDEO} from '../src/mediaTypes.js'; -import {STATUS} from '../src/constants.json'; const BIDDER_CODE = 'telaria'; const DOMAIN = 'tremorhub.com'; @@ -73,7 +71,7 @@ export const spec = { } }); } catch (error) { - utils.logError(error); + logError(error); width = 0; height = 0; } @@ -83,10 +81,12 @@ export const spec = { if (bidResult && bidResult.error) { errorMessage += `: ${bidResult.error}`; } - utils.logError(errorMessage); - } else if (!utils.isEmpty(bidResult.seatbid)) { + logError(errorMessage); + } else if (!isEmpty(bidResult.seatbid)) { bidResult.seatbid[0].bid.forEach(tag => { - bids.push(createBid(STATUS.GOOD, bidderRequest, tag, width, height, BIDDER_CODE)); + if (tag) { + bids.push(createBid(bidderRequest, tag, width, height)); + } }); } @@ -102,7 +102,7 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; if (syncOptions.pixelEnabled && serverResponses.length) { - (utils.deepAccess(serverResponses, '0.body.ext.telaria.userSync') || []).forEach(url => syncs.push({type: 'image', url: url})); + (deepAccess(serverResponses, '0.body.ext.telaria.userSync') || []).forEach(url => syncs.push({type: 'image', url: url})); } return syncs; }, @@ -114,7 +114,7 @@ export const spec = { onTimeout: function (timeoutData) { let url = getTimeoutUrl(timeoutData); if (url) { - utils.triggerPixel(url); + triggerPixel(url); } } }; @@ -135,7 +135,7 @@ function getEncodedValIfNotEmpty(val) { * @returns {string} */ function getSupplyChainAsUrlParam(schainObject) { - if (utils.isEmpty(schainObject)) { + if (isEmpty(schainObject)) { return ''; } @@ -157,22 +157,22 @@ function getSupplyChainAsUrlParam(schainObject) { function getUrlParams(params, schainFromBidRequest) { let urlSuffix = ''; - if (!utils.isEmpty(params)) { + if (!isEmpty(params)) { for (let key in params) { - if (key !== 'schain' && params.hasOwnProperty(key) && !utils.isEmpty(params[key])) { + if (key !== 'schain' && params.hasOwnProperty(key) && !isEmpty(params[key])) { urlSuffix += `&${key}=${params[key]}`; } } - urlSuffix += getSupplyChainAsUrlParam(!utils.isEmpty(schainFromBidRequest) ? schainFromBidRequest : params['schain']); + urlSuffix += getSupplyChainAsUrlParam(!isEmpty(schainFromBidRequest) ? schainFromBidRequest : params['schain']); } return urlSuffix; } export const getTimeoutUrl = function(timeoutData) { - let params = utils.deepAccess(timeoutData, '0.params.0'); + let params = deepAccess(timeoutData, '0.params.0'); - if (!utils.isEmpty(params)) { + if (!isEmpty(params)) { let url = `https://${EVENTS_ENDPOINT}`; params = Object.assign({ @@ -195,14 +195,14 @@ export const getTimeoutUrl = function(timeoutData) { * @returns {string} */ function generateUrl(bid, bidderRequest) { - let playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + let playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); if (!playerSize) { - utils.logWarn(`Although player size isn't required it is highly recommended`); + logWarn(`Although player size isn't required it is highly recommended`); } let width, height; if (playerSize) { - if (utils.isArray(playerSize) && (playerSize.length === 2) && (!isNaN(playerSize[0]) && !isNaN(playerSize[1]))) { + if (isArray(playerSize) && (playerSize.length === 2) && (!isNaN(playerSize[0]) && !isNaN(playerSize[1]))) { width = playerSize[0]; height = playerSize[1]; } else if (typeof playerSize === 'object') { @@ -211,8 +211,8 @@ function generateUrl(bid, bidderRequest) { } } - let supplyCode = utils.deepAccess(bid, 'params.supplyCode'); - let adCode = utils.deepAccess(bid, 'params.adCode'); + let supplyCode = deepAccess(bid, 'params.supplyCode'); + let adCode = deepAccess(bid, 'params.adCode'); if (supplyCode && adCode) { let url = `https://${supplyCode}.${TAG_ENDPOINT}?adCode=${adCode}`; @@ -253,38 +253,31 @@ function generateUrl(bid, bidderRequest) { } /** - * Create and return a bid object based on status and tag - * @param status + * Create and return a bid response * @param reqBid * @param response * @param width * @param height - * @param bidderCode */ -function createBid(status, reqBid, response, width, height, bidderCode) { - let bid = createBidFactory(status, reqBid); - +function createBid(reqBid, response, width, height) { // TTL 5 mins by default, future support for extended imp wait time - if (response) { - Object.assign(bid, { - requestId: reqBid.bidId, - cpm: response.price, - creativeId: response.crid || '-1', - vastXml: response.adm, - vastUrl: reqBid.vastUrl, - mediaType: 'video', - width: width, - height: height, - bidderCode: bidderCode, - currency: 'USD', - netRevenue: true, - ttl: 300, - ad: response.adm - }); - } - - bid.meta = bid.meta || {}; - if (response && response.adomain && response.adomain.length > 0) { + const bid = { + requestId: reqBid.bidId, + cpm: response.price, + creativeId: response.crid || '-1', + vastXml: response.adm, + vastUrl: reqBid.vastUrl, + mediaType: 'video', + width: width, + height: height, + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: response.adm, + meta: {} + }; + + if (response.adomain && response.adomain.length > 0) { bid.meta.advertiserDomains = response.adomain; } diff --git a/modules/temedyaBidAdapter.js b/modules/temedyaBidAdapter.js index 4577fb295cd..76a6234c095 100644 --- a/modules/temedyaBidAdapter.js +++ b/modules/temedyaBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { parseSizesInput, parseQueryStringParameters, logError } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; @@ -36,7 +36,7 @@ export const spec = { requestid: req.bidId }; if (mediaType === 'display') { - data.sizes = utils.parseSizesInput( + data.sizes = parseSizesInput( req.mediaTypes && req.mediaTypes.banner && req.mediaTypes.banner.sizes ).join('|') } @@ -44,7 +44,7 @@ export const spec = { return { method: ENDPOINT_METHOD, url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(data), + data: parseQueryStringParameters(data), options: { withCredentials: false, requestId: req.bidId, mediaType: mediaType } }; }); @@ -128,7 +128,7 @@ export const spec = { } return bidResponses; } catch (err) { - utils.logError(err); + logError(err); return []; } }, diff --git a/modules/terceptAnalyticsAdapter.js b/modules/terceptAnalyticsAdapter.js index 54da2bd06d2..748e512bd42 100644 --- a/modules/terceptAnalyticsAdapter.js +++ b/modules/terceptAnalyticsAdapter.js @@ -1,8 +1,8 @@ +import { parseSizesInput, getWindowLocation, buildUrl } 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 * as utils from '../src/utils.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -56,7 +56,7 @@ function mapBidRequests(params) { requestId: bid.bidderRequestId, auctionId: bid.auctionId, transactionId: bid.transactionId, - sizes: utils.parseSizesInput(bid.mediaTypes.banner.sizes).toString(), + sizes: parseSizesInput(bid.mediaTypes.banner.sizes).toString(), renderStatus: 1, requestTimestamp: params.auctionStart }); @@ -110,13 +110,13 @@ function mapBidResponse(bidResponse, status) { } function send(data, status) { - let location = utils.getWindowLocation(); + let location = getWindowLocation(); if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { Object.assign(data.auctionInit, { host: location.host, path: location.pathname, search: location.search }); } data.initOptions = initOptions; - let terceptAnalyticsRequestUrl = utils.buildUrl({ + let terceptAnalyticsRequestUrl = buildUrl({ protocol: 'https', hostname: (initOptions && initOptions.hostName) || defaultHostName, pathname: (initOptions && initOptions.pathName) || defaultPathName, diff --git a/modules/theAdxBidAdapter.js b/modules/theAdxBidAdapter.js index 91e36077e88..d7a79fe74d0 100644 --- a/modules/theAdxBidAdapter.js +++ b/modules/theAdxBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { logInfo, isEmpty, deepAccess, parseUrl, getDNT, parseSizesInput, _map } from '../src/utils.js'; import { BANNER, NATIVE, @@ -125,7 +125,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - utils.logInfo('theadx.isBidRequestValid', bid); + logInfo('theadx.isBidRequestValid', bid); let res = false; if (bid && bid.params) { res = !!(bid.params.pid && bid.params.tagId); @@ -141,10 +141,10 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - utils.logInfo('theadx.buildRequests', 'validBidRequests', validBidRequests, 'bidderRequest', bidderRequest); + logInfo('theadx.buildRequests', 'validBidRequests', validBidRequests, 'bidderRequest', bidderRequest); let results = []; const requestType = 'POST'; - if (!utils.isEmpty(validBidRequests)) { + if (!isEmpty(validBidRequests)) { results = validBidRequests.map( bidRequest => { return { @@ -176,7 +176,7 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: (serverResponse, request) => { - utils.logInfo('theadx.interpretResponse', 'serverResponse', serverResponse, ' request', request); + logInfo('theadx.interpretResponse', 'serverResponse', serverResponse, ' request', request); let responses = []; @@ -185,8 +185,8 @@ export const spec = { let seatBids = responseBody.seatbid; - if (!(utils.isEmpty(seatBids) || - utils.isEmpty(seatBids[0].bid))) { + if (!(isEmpty(seatBids) || + isEmpty(seatBids[0].bid))) { let seatBid = seatBids[0]; let bid = seatBid.bid[0]; @@ -280,7 +280,7 @@ export const spec = { * @return {UserSync[]} The user syncs which should be dropped. */ getUserSyncs: function (syncOptions, serverResponses) { - utils.logInfo('theadx.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses) + logInfo('theadx.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses) const syncs = []; if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { @@ -288,8 +288,8 @@ export const spec = { } serverResponses.forEach(resp => { - const syncIframeUrls = utils.deepAccess(resp, 'body.ext.sync.iframe'); - const syncImageUrls = utils.deepAccess(resp, 'body.ext.sync.image'); + const syncIframeUrls = deepAccess(resp, 'body.ext.sync.iframe'); + const syncImageUrls = deepAccess(resp, 'body.ext.sync.image'); if (syncOptions.iframeEnabled && syncIframeUrls) { syncIframeUrls.forEach(syncIframeUrl => { syncs.push({ @@ -314,7 +314,7 @@ export const spec = { } let buildSiteComponent = (bidRequest, bidderRequest) => { - let loc = utils.parseUrl(bidderRequest.refererInfo.referer, { + let loc = parseUrl(bidderRequest.refererInfo.referer, { decodeSearchAsString: true }); @@ -353,7 +353,7 @@ let buildDeviceComponent = (bidRequest, bidderRequest) => { language: ('language' in navigator) ? navigator.language : null, ua: ('userAgent' in navigator) ? navigator.userAgent : null, devicetype: isMobile() ? 1 : isConnectedTV() ? 3 : 2, - dnt: utils.getDNT() ? 1 : 0, + dnt: getDNT() ? 1 : 0, }; // Include connection info if available const CONNECTION = navigator.connection || navigator.webkitConnection; @@ -383,14 +383,14 @@ let extractValidSize = (bidRequest, bidderRequest) => { } else { requestedSizes = mediaTypes.video.sizes; } - } else if (!utils.isEmpty(bidRequest.sizes)) { + } else if (!isEmpty(bidRequest.sizes)) { requestedSizes = bidRequest.sizes } // Ensure the size array is normalized - let conformingSize = utils.parseSizesInput(requestedSizes); + let conformingSize = parseSizesInput(requestedSizes); - if (!utils.isEmpty(conformingSize) && conformingSize[0] != null) { + if (!isEmpty(conformingSize) && conformingSize[0] != null) { // Currently only the first size is utilized let splitSizes = conformingSize[0].split('x'); @@ -423,7 +423,7 @@ let generateBannerComponent = (bidRequest, bidderRequest) => { } let generateNativeComponent = (bidRequest, bidderRequest) => { - const assets = utils._map(bidRequest.mediaTypes.native, (bidParams, key) => { + const assets = _map(bidRequest.mediaTypes.native, (bidParams, key) => { const props = NATIVEPROBS[key]; const asset = { required: bidParams.required & 1, diff --git a/modules/timBidAdapter.js b/modules/timBidAdapter.js deleted file mode 100644 index 68c711f9935..00000000000 --- a/modules/timBidAdapter.js +++ /dev/null @@ -1,177 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import * as bidfactory from '../src/bidfactory.js'; -var CONSTANTS = require('../src/constants.json'); -const BIDDER_CODE = 'tim'; - -function parseBidRequest(bidRequest) { - let params = bidRequest.url.split('?')[1]; - var obj = {}; - var pairs = params.split('&'); - try { - for (var i in pairs) { - var split = pairs[i].split('='); - obj[decodeURIComponent(split[0])] = decodeURIComponent(split[1]); - } - } catch (e) { - utils.logError(e); - } - - return JSON.parse(obj.br); -} - -function formatAdMarkup(bid) { - var adm = bid.adm; - if ('nurl' in bid) { - adm += createTrackPixelHtml(bid.nurl); - } - return `${adm}`; -} - -function createTrackPixelHtml(url) { - if (!url) { - return ''; - } - let img = '
'; - img += '
'; - return img; -} - -export const spec = { - code: BIDDER_CODE, - aliases: ['timmedia'], - - isBidRequestValid: function(bid) { - if (bid.params && bid.params.publisherid && bid.params.placementCode) { - return true; - } if (!bid.params) { - utils.logError('bid not valid: params were not provided'); - } else if (!bid.params.publisherid) { - utils.logError('bid not valid: publisherid was not provided'); - } else if (!bid.params.placementCode) { - utils.logError('bid not valid: placementCode was not provided'); - } return false; - }, - - buildRequests: function(validBidRequests, bidderRequest) { - var requests = []; - for (var i = 0; i < validBidRequests.length; i++) { - requests.push(this.createRTBRequestURL(validBidRequests[i])); - } - return requests; - }, - - createRTBRequestURL: function(bidReq) { - // build bid request object - var domain = window.location.host; - var page = window.location.href; - var publisherid = bidReq.params.publisherid; - var bidFloor = bidReq.params.bidfloor; - var placementCode = bidReq.params.placementCode; - - var adW = bidReq.mediaTypes.banner.sizes[0][0]; - var adH = bidReq.mediaTypes.banner.sizes[0][1]; - - // build bid request with impressions - var bidRequest = { - id: utils.getUniqueIdentifierStr(), - imp: [{ - id: bidReq.bidId, - banner: { - w: adW, - h: adH - }, - tagid: placementCode, - bidfloor: bidFloor - }], - site: { - domain: domain, - page: page, - publisher: { - id: publisherid - } - }, - device: { - 'language': this.getLanguage(), - 'w': adW, - 'h': adH, - 'js': 1, - 'ua': navigator.userAgent - } - }; - if (!bidFloor) { - delete bidRequest.imp['bidfloor']; - } - - bidRequest.bidId = bidReq.bidId; - var url = 'https://hb.timmedia-hb.com/api/v2/services/prebid/' + publisherid + '/' + placementCode + '?' + 'br=' + encodeURIComponent(JSON.stringify(bidRequest)); - return { - method: 'GET', - url: url, - data: '', - options: {withCredentials: false} - }; - }, - - interpretResponse: function(serverResponse, bidRequest) { - bidRequest = parseBidRequest(bidRequest); - var bidResp = serverResponse.body; - const bidResponses = []; - if ((!bidResp || !bidResp.id) || - (!bidResp.seatbid || bidResp.seatbid.length === 0 || !bidResp.seatbid[0].bid || bidResp.seatbid[0].bid.length === 0)) { - return []; - } - bidResp.seatbid[0].bid.forEach(function (bidderBid) { - var responseCPM; - var placementCode = ''; - if (bidRequest) { - var bidResponse = bidfactory.createBid(1); - placementCode = bidRequest.placementCode; - bidRequest.status = CONSTANTS.STATUS.GOOD; - responseCPM = parseFloat(bidderBid.price); - if (responseCPM === 0) { - var bid = bidfactory.createBid(2); - bid.bidderCode = BIDDER_CODE; - bidResponses.push(bid); - return bidResponses; - } - bidResponse.placementCode = placementCode; - bidResponse.size = bidRequest.sizes; - bidResponse.creativeId = bidderBid.id; - bidResponse.bidderCode = BIDDER_CODE; - bidResponse.cpm = responseCPM; - bidResponse.ad = formatAdMarkup(bidderBid); - bidResponse.width = parseInt(bidderBid.w); - bidResponse.height = parseInt(bidderBid.h); - bidResponse.currency = bidResp.cur; - bidResponse.netRevenue = true; - bidResponse.requestId = bidRequest.bidId; - bidResponse.ttl = 180; - bidResponses.push(bidResponse); - } - }); - return bidResponses; - }, - getLanguage: function() { - const language = navigator.language ? 'language' : 'userLanguage'; - return navigator[language].split('-')[0]; - }, - - getUserSyncs: function(syncOptions, serverResponses) { - const syncs = [] - return syncs; - }, - - onTimeout: function(data) { - // Bidder specifc code - }, - - onBidWon: function(bid) { - // Bidder specific code - }, - - onSetTargeting: function(bid) { - // Bidder specific code - }, -} -registerBidder(spec); diff --git a/modules/timeoutRtdProvider.js b/modules/timeoutRtdProvider.js new file mode 100644 index 00000000000..323a5291e2d --- /dev/null +++ b/modules/timeoutRtdProvider.js @@ -0,0 +1,181 @@ + +import { submodule } from '../src/hook.js'; +import * as ajax from '../src/ajax.js'; +import { logInfo, deepAccess, logError } from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; + +const SUBMODULE_NAME = 'timeout'; + +// this allows the stubbing of functions during testing +export const timeoutRtdFunctions = { + getDeviceType, + getConnectionSpeed, + checkVideo, + calculateTimeoutModifier, + handleTimeoutIncrement +}; + +const entries = Object.entries || function(obj) { + const ownProps = Object.keys(obj); + let i = ownProps.length; + let resArray = new Array(i); + while (i--) { resArray[i] = [ownProps[i], obj[ownProps[i]]]; } + return resArray; +}; + +function getDeviceType() { + const userAgent = window.navigator.userAgent.toLowerCase(); + if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(userAgent))) { + return 5; // tablet + } + if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(userAgent))) { + return 4; // mobile + } + return 2; // personal computer +} + +function checkVideo(adUnits) { + return adUnits.some((adUnit) => { + return adUnit.mediaTypes && adUnit.mediaTypes.video; + }); +} + +function getConnectionSpeed() { + const connection = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection || {} + const connectionType = connection.type || connection.effectiveType; + + switch (connectionType) { + case 'slow-2g': + case '2g': + return 'slow'; + + case '3g': + return 'medium'; + + case 'bluetooth': + case 'cellular': + case 'ethernet': + case 'wifi': + case 'wimax': + case '4g': + return 'fast'; + } + + return 'unknown'; +} +/** + * Calculate the time to be added to the timeout + * @param {Array} adUnits + * @param {Object} rules + * @return {int} + */ +function calculateTimeoutModifier(adUnits, rules) { + logInfo('Timeout rules', rules); + let timeoutModifier = 0; + let toAdd = 0; + + if (rules.includesVideo) { + const hasVideo = timeoutRtdFunctions.checkVideo(adUnits); + toAdd = rules.includesVideo[hasVideo] || 0; + logInfo(`Adding ${toAdd} to timeout for includesVideo ${hasVideo}`) + timeoutModifier += toAdd; + } + + if (rules.numAdUnits) { + const numAdUnits = adUnits.length; + if (rules.numAdUnits[numAdUnits]) { + timeoutModifier += rules.numAdUnits[numAdUnits]; + } else { + for (const [rangeStr, timeoutVal] of entries(rules.numAdUnits)) { + const [lowerBound, upperBound] = rangeStr.split('-'); + if (parseInt(lowerBound) <= numAdUnits && numAdUnits <= parseInt(upperBound)) { + logInfo(`Adding ${timeoutVal} to timeout for numAdUnits ${numAdUnits}`) + timeoutModifier += timeoutVal; + break; + } + } + } + } + + if (rules.deviceType) { + const deviceType = timeoutRtdFunctions.getDeviceType(); + toAdd = rules.deviceType[deviceType] || 0; + logInfo(`Adding ${toAdd} to timeout for deviceType ${deviceType}`) + timeoutModifier += toAdd; + } + + if (rules.connectionSpeed) { + const connectionSpeed = timeoutRtdFunctions.getConnectionSpeed(); + toAdd = rules.connectionSpeed[connectionSpeed] || 0; + logInfo(`Adding ${toAdd} to timeout for connectionSpeed ${connectionSpeed}`) + timeoutModifier += toAdd; + } + + logInfo('timeout Modifier calculated', timeoutModifier); + return timeoutModifier; +} + +/** + * + * @param {Object} reqBidsConfigObj + * @param {function} callback + * @param {Object} config + * @param {Object} userConsent + */ +function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + logInfo('Timeout rtd config', config); + const timeoutUrl = deepAccess(config, 'params.endpoint.url'); + if (timeoutUrl) { + logInfo('Timeout url', timeoutUrl); + ajax.ajaxBuilder()(timeoutUrl, { + success: function(response) { + try { + const rules = JSON.parse(response); + timeoutRtdFunctions.handleTimeoutIncrement(reqBidsConfigObj, rules); + } catch (e) { + logError('Error parsing json response from timeout provider.') + } + callback(); + }, + error: function(errorStatus) { + logError('Timeout request error!', errorStatus); + callback(); + } + }); + } else if (deepAccess(config, 'params.rules')) { + timeoutRtdFunctions.handleTimeoutIncrement(reqBidsConfigObj, deepAccess(config, 'params.rules')); + callback(); + } else { + logInfo('No timeout endpoint or timeout rules found. Exiting timeout rtd module'); + callback(); + } +} + +/** + * Gets the timeout modifier, adds it to the bidder timeout, and sets it to reqBidsConfigObj + * @param {Object} reqBidsConfigObj + * @param {Object} rules + */ +function handleTimeoutIncrement(reqBidsConfigObj, rules) { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + const timeoutModifier = timeoutRtdFunctions.calculateTimeoutModifier(adUnits, rules); + const bidderTimeout = getGlobal().getConfig('bidderTimeout'); + reqBidsConfigObj.timeout = bidderTimeout + timeoutModifier; +} + +/** @type {RtdSubmodule} */ +export const timeoutSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: SUBMODULE_NAME, + init: () => true, + getBidRequestData, +}; + +function registerSubModule() { + submodule('realTimeData', timeoutSubmodule); +} + +registerSubModule(); diff --git a/modules/timeoutRtdProvider.md b/modules/timeoutRtdProvider.md new file mode 100644 index 00000000000..49d1e1fc70a --- /dev/null +++ b/modules/timeoutRtdProvider.md @@ -0,0 +1,151 @@ + --- + layout: page_v2 + title: Timeout Rtd Module + description: Module for managing timeouts in real time + page_type: module + module_type: rtd + module_code : example + enable_download : true + sidebarType : 1 + --- + +## Overview +The timeout RTD module enables publishers to set rules that determine the timeout based on +certain features. It supports rule sets dynamically retrieved from a timeout provider as well as rules +set directly via configuration. +Build the timeout RTD module into the Prebid.js package with: +``` +gulp build --modules=timeoutRtdProvider,rtdModule... +``` + +## Configuration +The module is configured in the realTimeData.dataProviders object. The module will override +`bidderTimeout` in the pbjs config. + +### Timeout Data Provider interface +The timeout RTD module provides an interface of dynamically fetching timeout rules from +a data provider just before the auction begins. The endpoint url is set in the config just as in +the example below, and the timeout data will be used when making bid requests. + +``` +pbjs.setConfig({ + ... + "realTimeData": { + "dataProviders": [{ + "name": 'timeout', + "params": { + "endpoint": { + "url": "http://{cdn-link}.json" + } + } + } + ]}, + + // This value below will be modified by the timeout RTD module if it successfully + // fetches the timeout data. + "bidderTimeout": 1500, + ... +}); +``` + +Sample Endpoint Response: +``` +{ + "rules": { + "includesVideo": { + "true": 200, + "false": 50 + }, + "numAdUnits" : { + "1-5": 100, + "6-10": 200, + "11-15": 300 + }, + "deviceType": { + "2": 50, + "4": 100, + "5": 200 + }, + "connectionSpeed": { + "slow": 200, + "medium": 100, + "fast": 50, + "unknown": 10 + }, +} +``` + +### Rule Handling: +The rules retrieved from the endpoint will be used to add time to the `bidderTimeout` based on certain features such as +the user's deviceType, connection speed, etc. These rules can also be configured statically on page via a `rules` object. +Note that the timeout Module will ignore the static rules if an endpoint url is provided. The timeout rules follow the +format: +``` +{ + '': { + '': + } +} +``` +See bottom of page for examples. + +Currently supported features: + +|Name |Description | Keys | Example +| :------------ | :------------ | :------------ |:------------ | +| includesVideo | Adds time to the timeout based on whether there is a video ad unit in the auction or not | 'true'/'false'| { "true": 200, "false": 50 } | +| numAdUnits | Adds time based on the number of ad units. Ranges in the format `'lowerbound-upperbound` are accepted. This range is inclusive | numbers or number ranges | {"1": 50, "2-5": 100, "6-10": 200} | +| deviceType | Adds time based on device type| 2, 4, or 5| {"2": 50, "4": 100} | +| connectionSpeed | Adds time based on connection speed. `connectionSpeed` defaults to 'unknown' if connection speed cannot be determined | slow, medium, fast, or unknown | { "slow": 200} | + +If there are multiple rules set, all of them would be used and any that apply will be added to the base timeout. For example, if the rules object contains: +``` +{ + "includesVideo": { + "true": 200, + "false": 50 + }, + "numAdUnits" : { + "1-3": 100, + "4-5": 200 + } +} +``` +and there are 3 ad units in the auction, all of which are banner, then the timeout to be added will be 150 milliseconds (50 for `includesVideo[false]` + 100 for `numAdUnits['1-3']`). + +Full example: +``` +pbjs.setConfig({ + ... + "realTimeData": { + "dataProviders": [{ + "name": 'timeout', + "params": { + "rules": { + "includesVideo": { + "true": 200, + "false": 50 + }, + "numAdUnits" : { + "1-5": 100, + "6-10": 200, + "11-15": 300 + }, + "deviceType": { + "2": 50, + "4": 100, + "5": 200 + }, + "connectionSpeed": { + "slow": 200, + "medium": 100, + "fast": 50, + "unknown": 10 + }, + } + } + ]} + ... + // The timeout RTD module will add time to `bidderTimeout` based on the rules set above. + "bidderTimeout": 1500, +``` diff --git a/modules/topRTBBidAdapter.js b/modules/topRTBBidAdapter.js deleted file mode 100644 index c93bd8ccaac..00000000000 --- a/modules/topRTBBidAdapter.js +++ /dev/null @@ -1,65 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'topRTB'; -const ENDPOINT_URL = 'https://ssp.toprtb.com/ssp/rest/ReqAd?ref=www.google.com&hbid=0&adUnitId='; -var adName = ''; -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid: function (bid) { - if (utils.deepAccess(bid, 'mediaTypes.banner')) { - adName = 'banner'; - return bid.params && !!bid.params.adUnitId; - } - if (utils.deepAccess(bid, 'mediaTypes.video')) { - adName = 'video'; - return bid.params && !!bid.params.adUnitId; - } - }, - - buildRequests: function (validBidRequests, bidderRequest) { - let adunitid = []; - utils._each(validBidRequests, function (bid) { - adunitid.push(bid.params.adUnitId + '_' + bid.bidId); - }); - - return { - method: 'GET', - url: ENDPOINT_URL + adunitid.toString() - }; - }, - - interpretResponse: function(serverResponses, request) { - const bidResponses = []; - utils._each(serverResponses.body, function(response) { - if (response.cpm > 0) { - const bidResponse = { - requestId: response.bidId, - cpm: response.cpm, - width: response.width, - height: response.height, - ad: response.mediadata, - ttl: response.ttl, - creativeId: response.id, - netRevenue: true, - currency: response.currency, - tracking: response.tracking, - impression: response.impression - }; - if (adName == 'video') { - bidResponse.vastXml = response.mediadata; - bidResponse.mediaType = 'video'; - } else { - bidResponse.ad = response.mediadata; - bidResponse.mediaType = 'banner'; - } - bidResponses.push(bidResponse); - } - }); - return bidResponses; - } -}; - -registerBidder(spec); diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index ec9d30c0e29..006357cd4b9 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -1,6 +1,6 @@ /* eslint-disable no-tabs */ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; +import { parseUrl, deepAccess } from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; export const ADAPTER_VERSION = '1'; @@ -72,7 +72,7 @@ registerBidder(spec); * Creates site description object */ function createSite(refInfo) { - let url = utils.parseUrl(refInfo.referer); + let url = parseUrl(refInfo.referer); let site = { 'domain': url.hostname, 'page': url.protocol + '://' + url.hostname + url.pathname @@ -102,7 +102,7 @@ function parseSizes(sizes) { } function getBannerSizes(bidRequest) { - return parseSizes(utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes); + return parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes); } function bidToRequest(bid) { diff --git a/modules/trendqubeBidAdapter.js b/modules/trendqubeBidAdapter.js deleted file mode 100644 index d0364b4acec..00000000000 --- a/modules/trendqubeBidAdapter.js +++ /dev/null @@ -1,102 +0,0 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; - -const BIDDER_CODE = 'trendqube'; -const AD_URL = 'https://ads.trendqube.com/?c=o&m=multi'; - -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; - } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl); - case NATIVE: - return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); - default: - return false; - } -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], - - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - let winTop = window; - let location; - try { - location = new URL(bidderRequest.refererInfo.referer) - winTop = window.top; - } catch (e) { - location = winTop.location; - utils.logMessage(e); - }; - let placements = []; - let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent - } - } - const len = validBidRequests.length; - - for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; - let sizes - if (bid.mediaTypes) { - if (bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { - sizes = bid.mediaTypes[BANNER].sizes - } else if (bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { - sizes = bid.mediaTypes[VIDEO].playerSize - } - } - placements.push({ - placementId: bid.params.placementId, - bidId: bid.bidId, - sizes: sizes || [], - wPlayer: sizes ? sizes[0] : 0, - hPlayer: sizes ? sizes[1] : 0, - traffic: bid.params.traffic || BANNER, - schain: bid.schain || {} - }); - } - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - response.push(resItem); - } - } - return response; - }, -}; - -registerBidder(spec); diff --git a/modules/tribeosBidAdapter.js b/modules/tribeosBidAdapter.js deleted file mode 100644 index 80361aa3fdb..00000000000 --- a/modules/tribeosBidAdapter.js +++ /dev/null @@ -1,165 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import * as bidfactory from '../src/bidfactory.js'; -import {BANNER} from '../src/mediaTypes.js'; -var CONSTANTS = require('../src/constants.json'); - -const BIDDER_CODE = 'tribeos'; -const ENDPOINT_URL = 'https://bidder.tribeos.tech/prebid/'; -const LOG_PREFIX = 'TRIBEOS: '; -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} - * bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - if (utils.isEmpty(bid.params.placementId)) { - utils.logError(LOG_PREFIX, 'placementId is required, please contact tribeOS for placementId. Bid details: ', JSON.stringify(bid)); - return false; - } - return true; - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - - * an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests) { - var requests = []; - for (var i = 0; i < validBidRequests.length; i++) { - requests.push(this.buidRTBRequest(validBidRequests[i])); - } - return requests; - }, - buidRTBRequest: function(bidReq) { - // build bid request object - - var placementId = bidReq.params.placementId; - var bidFloor = bidReq.params.bidfloor; - var placementCode = bidReq.params.placementCode; - - var adWidth = bidReq.mediaTypes.banner.sizes[0][0]; - var adHeight = bidReq.mediaTypes.banner.sizes[0][1]; - - // build bid request with impressions - var bidRequest = { - id: utils.getUniqueIdentifierStr(), - imp: [{ - id: bidReq.bidId, - banner: { - w: adWidth, - h: adHeight - }, - tagid: placementCode, - bidfloor: bidFloor - }], - site: { - domain: window.location.host, - page: window.location.href, - publisher: { - id: placementId - } - }, - device: { - 'language': (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), - 'w': adWidth, - 'h': adHeight, - 'js': 1, - 'ua': navigator.userAgent - } - }; - - // apply gdpr - if (bidReq.gdprConsent) { - bidRequest.regs = {ext: {gdpr: bidReq.gdprConsent.gdprApplies ? 1 : 0}}; - bidRequest.user = {ext: {consent: bidReq.gdprConsent.consentString}}; - } - - bidRequest.bidId = bidReq.bidId; - var url = ENDPOINT_URL + placementId + '/requests'; - if (!utils.isEmpty(bidReq.params.endpointUrl)) { - url = bidReq.params.endpointUrl + placementId + '/requests'; - } - - return { - method: 'POST', - url: url, - data: JSON.stringify(bidRequest), - options: { withCredentials: true, contentType: 'application/json' }, - }; - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} - * serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, bidRequest) { - const responseBody = serverResponse.body; - - utils.logInfo(LOG_PREFIX, 'response body: ', JSON.stringify(serverResponse)); - - if ((!responseBody || !responseBody.id)) { - return []; - } - const bidResponses = []; - responseBody.seatbid[0].bid.forEach(function(bidderBid) { - var responsePrice; - var placementCode = ''; - if (bidRequest) { - var bidResponse = bidfactory.createBid(1); - placementCode = bidRequest.placementCode; - bidRequest.status = CONSTANTS.STATUS.GOOD; - responsePrice = parseFloat(bidderBid.price); - if (responsePrice === 0) { - var bid = bidfactory.createBid(2); - bid.bidderCode = BIDDER_CODE; - bidResponses.push(bid); - - utils.logInfo(LOG_PREFIX, 'response price is zero. Response data: ', JSON.stringify(bidRequest)); - - return bidResponses; - } - bidResponse.placementCode = placementCode; - bidResponse.size = bidRequest.sizes; - bidResponse.creativeId = bidderBid.crid; - bidResponse.bidderCode = BIDDER_CODE; - bidResponse.cpm = responsePrice; - bidResponse.ad = bidderBid.adm; - bidResponse.width = parseInt(bidderBid.w); - bidResponse.height = parseInt(bidderBid.h); - bidResponse.currency = responseBody.cur; - bidResponse.netRevenue = true; - bidResponse.requestId = bidderBid.impid; - bidResponse.ttl = 180; - - utils.logInfo(LOG_PREFIX, 'bid response data: ', JSON.stringify(bidResponse)); - utils.logInfo(LOG_PREFIX, 'bid request data: ', JSON.stringify(bidRequest)); - - bidResponses.push(bidResponse); - } - }); - return bidResponses; - }, - /** - * Register bidder specific code, which will execute if a bid from this - * bidder won the auction - * - * @param {Bid} - * The bid that won the auction - */ -// onBidWon: function(bid) { -// ajax(this.nurls[bid.requestId], null); -// } - -} -registerBidder(spec); diff --git a/modules/trionBidAdapter.js b/modules/trionBidAdapter.js index 37d54e60cd2..dd1624f90d7 100644 --- a/modules/trionBidAdapter.js +++ b/modules/trionBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { getBidIdParameter, parseSizesInput, tryAppendQueryString } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -53,6 +53,9 @@ export const spec = { bid.creativeId = result.creativeId; bid.currency = result.currency; bid.netRevenue = result.netRevenue; + if (result.adomain) { + bid.meta = {advertiserDomains: result.adomain} + } bidResponses.push(bid); } } @@ -109,11 +112,11 @@ function getPublisherUrl() { } function buildTrionUrlParams(bid, bidderRequest) { - var pubId = utils.getBidIdParameter('pubId', bid.params); - var sectionId = utils.getBidIdParameter('sectionId', bid.params); + var pubId = getBidIdParameter('pubId', bid.params); + var sectionId = getBidIdParameter('sectionId', bid.params); var url = getPublisherUrl(); var bidSizes = getBidSizesFromBidRequest(bid); - var sizes = utils.parseSizesInput(bidSizes).join(','); + var sizes = parseSizesInput(bidSizes).join(','); var isAutomated = (navigator && navigator.webdriver) ? '1' : '0'; var isHidden = (document.hidden) ? '1' : '0'; var visibilityState = encodeURIComponent(document.visibilityState); @@ -128,10 +131,10 @@ function buildTrionUrlParams(bid, bidderRequest) { setStorageData(BASE_KEY + 'lps', pubId + ':' + sectionId); var trionUrl = ''; - trionUrl = utils.tryAppendQueryString(trionUrl, 'bidId', bid.bidId); - trionUrl = utils.tryAppendQueryString(trionUrl, 'pubId', pubId); - trionUrl = utils.tryAppendQueryString(trionUrl, 'sectionId', sectionId); - trionUrl = utils.tryAppendQueryString(trionUrl, 'vers', '$prebid.version$'); + trionUrl = tryAppendQueryString(trionUrl, 'bidId', bid.bidId); + trionUrl = tryAppendQueryString(trionUrl, 'pubId', pubId); + trionUrl = tryAppendQueryString(trionUrl, 'sectionId', sectionId); + trionUrl = tryAppendQueryString(trionUrl, 'vers', '$prebid.version$'); if (url) { trionUrl += 'url=' + url + '&'; } @@ -139,22 +142,22 @@ function buildTrionUrlParams(bid, bidderRequest) { trionUrl += 'sizes=' + sizes + '&'; } if (intT) { - trionUrl = utils.tryAppendQueryString(trionUrl, 'int_t', encodeURIComponent(intT)); + trionUrl = tryAppendQueryString(trionUrl, 'int_t', encodeURIComponent(intT)); } - trionUrl = utils.tryAppendQueryString(trionUrl, 'tr_wd', isAutomated); - trionUrl = utils.tryAppendQueryString(trionUrl, 'tr_hd', isHidden); - trionUrl = utils.tryAppendQueryString(trionUrl, 'tr_vs', visibilityState); + trionUrl = tryAppendQueryString(trionUrl, 'tr_wd', isAutomated); + trionUrl = tryAppendQueryString(trionUrl, 'tr_hd', isHidden); + trionUrl = tryAppendQueryString(trionUrl, 'tr_vs', visibilityState); if (bidderRequest && bidderRequest.gdprConsent) { var gdpr = bidderRequest.gdprConsent; if (gdpr) { if (gdpr.consentString) { - trionUrl = utils.tryAppendQueryString(trionUrl, 'gdprc', encodeURIComponent(gdpr.consentString)); + trionUrl = tryAppendQueryString(trionUrl, 'gdprc', encodeURIComponent(gdpr.consentString)); } - trionUrl = utils.tryAppendQueryString(trionUrl, 'gdpr', (gdpr.gdprApplies ? 1 : 0)); + trionUrl = tryAppendQueryString(trionUrl, 'gdpr', (gdpr.gdprApplies ? 1 : 0)); } } if (bidderRequest && bidderRequest.uspConsent) { - trionUrl = utils.tryAppendQueryString(trionUrl, 'usp', encodeURIComponent(bidderRequest.uspConsent)); + trionUrl = tryAppendQueryString(trionUrl, 'usp', encodeURIComponent(bidderRequest.uspConsent)); } // remove the trailing "&" if (trionUrl.lastIndexOf('&') === trionUrl.length - 1) { diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index e8d248eea03..215769e9812 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -1,11 +1,13 @@ +import { tryAppendQueryString, logMessage, isEmpty, isStr, isPlainObject, isArray, logWarn } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; const GVLID = 28; const BIDDER_CODE = 'triplelift'; const STR_ENDPOINT = 'https://tlx.3lift.com/header/auction?'; +const BANNER_TIME_TO_LIVE = 300; +const INSTREAM_TIME_TO_LIVE = 3600; let gdprApplies = true; let consentString = null; @@ -21,41 +23,41 @@ export const tripleliftAdapterSpec = { let tlCall = STR_ENDPOINT; let data = _buildPostBody(bidRequests); - tlCall = utils.tryAppendQueryString(tlCall, 'lib', 'prebid'); - tlCall = utils.tryAppendQueryString(tlCall, 'v', '$prebid.version$'); + tlCall = tryAppendQueryString(tlCall, 'lib', 'prebid'); + tlCall = tryAppendQueryString(tlCall, 'v', '$prebid.version$'); if (bidderRequest && bidderRequest.refererInfo) { let referrer = bidderRequest.refererInfo.referer; - tlCall = utils.tryAppendQueryString(tlCall, 'referrer', referrer); + tlCall = tryAppendQueryString(tlCall, 'referrer', referrer); } if (bidderRequest && bidderRequest.timeout) { - tlCall = utils.tryAppendQueryString(tlCall, 'tmax', bidderRequest.timeout); + tlCall = tryAppendQueryString(tlCall, 'tmax', bidderRequest.timeout); } if (bidderRequest && bidderRequest.gdprConsent) { if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { gdprApplies = bidderRequest.gdprConsent.gdprApplies; - tlCall = utils.tryAppendQueryString(tlCall, 'gdpr', gdprApplies.toString()); + tlCall = tryAppendQueryString(tlCall, 'gdpr', gdprApplies.toString()); } if (typeof bidderRequest.gdprConsent.consentString !== 'undefined') { consentString = bidderRequest.gdprConsent.consentString; - tlCall = utils.tryAppendQueryString(tlCall, 'cmp_cs', consentString); + tlCall = tryAppendQueryString(tlCall, 'cmp_cs', consentString); } } if (bidderRequest && bidderRequest.uspConsent) { - tlCall = utils.tryAppendQueryString(tlCall, 'us_privacy', bidderRequest.uspConsent); + tlCall = tryAppendQueryString(tlCall, 'us_privacy', bidderRequest.uspConsent); } if (config.getConfig('coppa') === true) { - tlCall = utils.tryAppendQueryString(tlCall, 'coppa', true); + tlCall = tryAppendQueryString(tlCall, 'coppa', true); } if (tlCall.lastIndexOf('&') === tlCall.length - 1) { tlCall = tlCall.substring(0, tlCall.length - 1); } - utils.logMessage('tlCall request built: ' + tlCall); + logMessage('tlCall request built: ' + tlCall); return { method: 'POST', @@ -79,17 +81,17 @@ export const tripleliftAdapterSpec = { let syncEndpoint = 'https://eb2.3lift.com/sync?'; if (syncType === 'image') { - syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'px', 1); - syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'src', 'prebid'); + syncEndpoint = tryAppendQueryString(syncEndpoint, 'px', 1); + syncEndpoint = tryAppendQueryString(syncEndpoint, 'src', 'prebid'); } if (consentString !== null) { - syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'gdpr', gdprApplies); - syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'cmp_cs', consentString); + syncEndpoint = tryAppendQueryString(syncEndpoint, 'gdpr', gdprApplies); + syncEndpoint = tryAppendQueryString(syncEndpoint, 'cmp_cs', consentString); } if (usPrivacy) { - syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'us_privacy', usPrivacy); + syncEndpoint = tryAppendQueryString(syncEndpoint, 'us_privacy', usPrivacy); } return [{ @@ -122,7 +124,7 @@ function _buildPostBody(bidRequests) { } else if (bidRequest.mediaTypes.banner) { imp.banner = { format: _sizes(bidRequest.sizes) }; }; - if (!utils.isEmpty(bidRequest.ortb2Imp)) { + if (!isEmpty(bidRequest.ortb2Imp)) { imp.fpd = _getAdUnitFpd(bidRequest.ortb2Imp); } return imp; @@ -143,7 +145,7 @@ function _buildPostBody(bidRequests) { let ext = _getExt(schain, globalFpd); - if (!utils.isEmpty(ext)) { + if (!isEmpty(ext)) { data.ext = ext; } return data; @@ -198,10 +200,10 @@ function _getGlobalFpd() { _addEntries(context, fpdContext); _addEntries(user, fpdUser); - if (!utils.isEmpty(context)) { + if (!isEmpty(context)) { fpd.context = context; } - if (!utils.isEmpty(user)) { + if (!isEmpty(user)) { fpd.user = user; } return fpd; @@ -213,7 +215,7 @@ function _getAdUnitFpd(adUnitFpd) { _addEntries(context, adUnitFpd.ext); - if (!utils.isEmpty(context)) { + if (!isEmpty(context)) { fpd.context = context; } @@ -221,7 +223,7 @@ function _getAdUnitFpd(adUnitFpd) { } function _addEntries(target, source) { - if (!utils.isEmpty(source)) { + if (!isEmpty(source)) { Object.keys(source).forEach(key => { if (source[key] != null) { target[key] = source[key]; @@ -232,10 +234,10 @@ function _addEntries(target, source) { function _getExt(schain, fpd) { let ext = {}; - if (!utils.isEmpty(schain)) { + if (!isEmpty(schain)) { ext.schain = { ...schain }; } - if (!utils.isEmpty(fpd)) { + if (!isEmpty(fpd)) { ext.fpd = { ...fpd }; } return ext; @@ -260,19 +262,36 @@ function getPubCommonEids(bidRequest) { function getEids(bidRequest, type, source, rtiPartner) { return bidRequest .map(getUserId(type)) // bids -> userIds of a certain type - .filter((x) => !!x) // filter out null userIds + .filter(filterEids(type)) // filter out unqualified userIds .map(formatEid(source, rtiPartner)); // userIds -> eid objects } +const filterEids = type => (userId, i, arr) => { + let isValidUserId = + !!userId && // is not null nor empty + (isStr(userId) + ? !!userId + : isPlainObject(userId) && // or, is object + !isArray(userId) && // not an array + !isEmpty(userId) && // is not empty + userId.id && // contains nested id field + isStr(userId.id) && // nested id field is a string + !!userId.id); // that is not empty + if (!isValidUserId && arr[0] !== undefined) { + logWarn(`Triplelift: invalid ${type} userId format`); + } + return isValidUserId; +}; + function getUserId(type) { - return (bid) => (bid && bid.userId && bid.userId[type]); + return bid => bid && bid.userId && bid.userId[type]; } function formatEid(source, rtiPartner) { - return (id) => ({ + return (userId) => ({ source, uids: [{ - id, + id: userId.id ? userId.id : userId, ext: { rtiPartner } }] }); @@ -311,7 +330,7 @@ function _buildResponseObject(bidderRequest, bid) { creativeId: creativeId, dealId: dealId, currency: 'USD', - ttl: 300, + ttl: BANNER_TIME_TO_LIVE, tl_source: bid.tl_source, meta: {} }; @@ -319,6 +338,7 @@ function _buildResponseObject(bidderRequest, bid) { if (_isInstreamBidRequest(breq)) { bidResponse.vastXml = bid.ad; bidResponse.mediaType = 'video'; + bidResponse.ttl = INSTREAM_TIME_TO_LIVE; }; if (bid.advertiser_name) { diff --git a/modules/truereachBidAdapter.js b/modules/truereachBidAdapter.js index 92f9cc9951e..a0244e3a349 100755 --- a/modules/truereachBidAdapter.js +++ b/modules/truereachBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { deepAccess, getUniqueIdentifierStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; @@ -13,7 +13,7 @@ export const spec = { isBidRequestValid: function (bidRequest) { return (bidRequest.params.site_id && bidRequest.params.bidfloor && - utils.deepAccess(bidRequest, 'mediaTypes.banner') && (utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0)); + deepAccess(bidRequest, 'mediaTypes.banner') && (deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0)); }, buildRequests: function (validBidRequests, bidderRequest) { @@ -23,7 +23,7 @@ export const spec = { let queryParams = buildCommonQueryParamsFromBids(validBidRequests, bidderRequest); - let siteId = utils.deepAccess(validBidRequests[0], 'params.site_id'); + let siteId = deepAccess(validBidRequests[0], 'params.site_id'); let url = BIDDER_URL + siteId + '?hb=1&transactionId=' + validBidRequests[0].transactionId; @@ -115,13 +115,13 @@ function buildCommonQueryParamsFromBids(validBidRequests, bidderRequest) { adH = adSizes[0][1]; } - let bidFloor = Number(utils.deepAccess(validBidRequests[0], 'params.bidfloor')); + let bidFloor = Number(0); let domain = window.location.host; let page = window.location.host + window.location.pathname + location.search + location.hash; let defaultParams = { - id: utils.getUniqueIdentifierStr(), + id: getUniqueIdentifierStr(), imp: [ { id: validBidRequests[0].bidId, diff --git a/modules/trustpidSystem.js b/modules/trustpidSystem.js new file mode 100644 index 00000000000..74b522e8f1a --- /dev/null +++ b/modules/trustpidSystem.js @@ -0,0 +1,197 @@ +/** + * This module adds TrustPid provided by Vodafone Sales and Services Limited to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/trustpidSystem + * @requires module:modules/userId + */ +import { logInfo, logError } from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'trustpid'; +const LOG_PREFIX = 'Trustpid module' +let mnoAcronym = ''; +let mnoDomain = ''; + +export const storage = getStorageManager(null, MODULE_NAME); + +/** + * Handle an event for an iframe. + * Takes the body.url parameter from event and returns the string domain. + * i.e.: "fc.vodafone.de" + * @param event + */ +function messageHandler(event) { + let msg; + try { + if (event && event.data && typeof event.data === 'string' && event.data) { + 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('/'); + mnoDomain = domainURL[0]; + logInfo(`${LOG_PREFIX}: Message handler set domain to ${mnoDomain}`); + getDomainAcronym(mnoDomain); + } + } + } catch (e) { + logError(e); + } +} + +/** + * Properly sets the trustpid acronym depending on the domain value. + * @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'; + } + return mnoAcronym = prefix + acronym; +} + +// Set a listener to handle the iframe response message. +window.addEventListener('message', messageHandler, false); + +/** + * Get the "umid" from html5 local storage to make it available to the UserId module. + * @param config + * @returns {{trustpid: (*|string), acr: (string)}} + */ +function getTrustpidFromStorage() { + // Get the domain either from localStorage or global + let domain = JSON.parse(storage.getDataFromLocalStorage('fcIdConnectDomain')) || mnoDomain; + logInfo(`${LOG_PREFIX}: Local storage domain: ${domain}`); + + if (!domain) { + logInfo(`${LOG_PREFIX}: Local storage domain not found, returning null`); + return { + trustpid: null, + acr: null, + }; + } + + // Get the acronym from global + let acronym = mnoAcronym; + // if acronym is empty, but "domain" is available, get the acronym from domain + if (!acronym) { + getDomainAcronym(domain); + acronym = mnoAcronym; + } + + logInfo(`${LOG_PREFIX}: Domain acronym found: ${acronym}`); + + // Domain is correct in both local storage and idGraph, but no acronym is existing for the domain + if (domain && !acronym) { + return { + trustpid: null, + acr: null + } + } + + let fcIdConnectObject; + let fcIdConnectData = JSON.parse(storage.getDataFromLocalStorage('fcIdConnectData')); + logInfo(`${LOG_PREFIX}: Local storage fcIdConnectData: ${JSON.stringify(fcIdConnectData)}`); + + if (fcIdConnectData && + fcIdConnectData.connectId && + Array.isArray(fcIdConnectData.connectId.idGraph) && + fcIdConnectData.connectId.idGraph.length > 0) { + fcIdConnectObject = fcIdConnectData.connectId.idGraph.find(item => { + return item.domain === domain; + }); + } + logInfo(`${LOG_PREFIX}: Local storage fcIdConnectObject for domain: ${JSON.stringify(fcIdConnectObject)}`); + + return { + trustpid: (fcIdConnectObject && fcIdConnectObject.umid) + ? fcIdConnectObject.umid + : null, + acr: acronym, + }; +} + +/** @type {Submodule} */ +export const trustpidSubmodule = { + /** + * Used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * Decodes the stored id value for passing to bid requests. + * @function + * @returns {{trustpid: string} | null} + */ + decode(bidId) { + logInfo(`${LOG_PREFIX}: Decoded ID value ${JSON.stringify(bidId)}`); + return bidId.trustpid ? bidId : null; + }, + /** + * Get the id from helper function and initiate a new user sync. + * @param config + * @returns {{callback: result}|{id: {trustpid: string}}} + */ + getId: function(config) { + const data = getTrustpidFromStorage(); + if (data.trustpid) { + logInfo(`${LOG_PREFIX}: Local storage ID value ${JSON.stringify(data)}`); + return {id: {trustpid: data.trustpid + data.acr}}; + } else { + if (!config) { + config = {}; + } + if (!config.params) { + config.params = {}; + } + if (typeof config.params.maxDelayTime === 'undefined' || config.params.maxDelayTime === null) { + config.params.maxDelayTime = 1000; + } + // Current delay and delay step in milliseconds + let currentDelay = 0; + const delayStep = 50; + const result = (callback) => { + const data = getTrustpidFromStorage(); + if (!data.trustpid) { + if (currentDelay > config.params.maxDelayTime) { + logInfo(`${LOG_PREFIX}: No trustpid value set after ${config.params.maxDelayTime} max allowed delay time`); + callback(null); + } else { + currentDelay += delayStep; + setTimeout(() => { + result(callback); + }, delayStep); + } + } else { + const dataToReturn = { trustpid: data.trustpid + data.acr }; + logInfo(`${LOG_PREFIX}: Returning ID value data of ${JSON.stringify(dataToReturn)}`); + callback(dataToReturn); + } + }; + return { callback: result }; + } + }, +}; + +submodule('userId', trustpidSubmodule); diff --git a/modules/trustpidSystem.md b/modules/trustpidSystem.md new file mode 100644 index 00000000000..c4309c9d807 --- /dev/null +++ b/modules/trustpidSystem.md @@ -0,0 +1,45 @@ +## trustpid User Id Submodule + +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 + }, + } + ], + } +}); +``` + +## 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 diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index 3116400edf7..70cae0f50f1 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -1,14 +1,13 @@ -import * as utils from '../src/utils.js'; +import { isEmpty, deepAccess, logError, logWarn, parseGPTSingleSizeArrayToRtbSize } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'trustx'; -const ENDPOINT_URL = 'https://sofia.trustx.org/hb'; -const NEW_ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson?sp=trustx'; +const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson?sp=trustx'; const TIME_TO_LIVE = 360; -const ADAPTER_SYNC_URL = 'https://sofia.trustx.org/push_sync'; +const ADAPTER_SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; const LOG_ERROR_MESS = { @@ -38,40 +37,231 @@ export const spec = { * Make a server request from the list of BidRequests. * * @param {BidRequest[]} validBidRequests - an array of bids - * @param {bidderRequest} - bidder request object + * @param {bidderRequest} bidderRequest bidder request object * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - const bids = validBidRequests || []; - const newFormatBids = []; - const oldFormatBids = []; - const requests = []; - bids.forEach(bid => { - if (bid.params.useNewFormat) { - newFormatBids.push(bid); - } else { - oldFormatBids.push(bid); + if (!validBidRequests.length) { + return null; + } + let pageKeywords = null; + let jwpseg = null; + let permutiveseg = null; + let content = null; + let schain = null; + let userId = null; + let userIdAsEids = null; + let user = null; + let userExt = null; + let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; + + const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; + const imp = []; + const bidsMap = {}; + + validBidRequests.forEach((bid) => { + if (!bidderRequestId) { + bidderRequestId = bid.bidderRequestId; + } + if (!auctionId) { + auctionId = bid.auctionId; + } + if (!schain) { + schain = bid.schain; + } + if (!userId) { + userId = bid.userId; + } + if (!userIdAsEids) { + userIdAsEids = bid.userIdAsEids; + } + const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp} = bid; + bidsMap[bidId] = bid; + const bidFloor = _getFloor(mediaTypes || {}, bid); + if (rtd) { + const jwTargeting = rtd.jwplayer && rtd.jwplayer.targeting; + if (jwTargeting) { + if (!jwpseg && jwTargeting.segments) { + jwpseg = jwTargeting.segments; + } + if (!content && jwTargeting.content) { + content = jwTargeting.content; + } + } + const permutiveTargeting = rtd.p_standard && rtd.p_standard.targeting; + if (!permutiveseg && permutiveTargeting && permutiveTargeting.segments) { + permutiveseg = permutiveTargeting.segments; + } + } + let impObj = { + id: bidId && bidId.toString(), + tagid: uid.toString(), + ext: { + divid: adUnitCode && adUnitCode.toString() + } + }; + + if (ortb2Imp) { + if (ortb2Imp.instl) { + impObj.instl = ortb2Imp.instl; + } + if (ortb2Imp.ext && ortb2Imp.ext.data) { + impObj.ext.data = ortb2Imp.ext.data; + if (impObj.ext.data.adserver && impObj.ext.data.adserver.adslot) { + impObj.ext.gpid = impObj.ext.data.adserver.adslot.toString(); + } else { + impObj.ext.gpid = ortb2Imp.ext.data.pbadslot && ortb2Imp.ext.data.pbadslot.toString(); + } + } + } + + if (!isEmpty(keywords)) { + if (!pageKeywords) { + pageKeywords = keywords; + } + impObj.ext.bidder = { keywords }; + } + + if (bidFloor) { + impObj.bidfloor = bidFloor; + } + + if (!mediaTypes || mediaTypes[BANNER]) { + const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); + if (banner) { + impObj.banner = banner; + } + } + if (mediaTypes && mediaTypes[VIDEO]) { + const video = createVideoRequest(bid, mediaTypes[VIDEO]); + if (video) { + impObj.video = video; + } + } + + if (impObj.banner || impObj.video) { + imp.push(impObj); } }); - if (newFormatBids.length) { - const newFormatRequests = newFormatRequest(newFormatBids, bidderRequest); - if (newFormatRequests) { - requests.push(newFormatRequests); + + const source = { + tid: auctionId && auctionId.toString(), + ext: { + wrapper: 'Prebid_js', + wrapper_version: '$prebid.version$' + } + }; + + if (schain) { + source.ext.schain = schain; + } + + const bidderTimeout = config.getConfig('bidderTimeout') || timeout; + const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; + + let request = { + id: bidderRequestId && bidderRequestId.toString(), + site: { + page: referer + }, + tmax, + source, + imp + }; + + if (content) { + 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) { + user = { + data: userData + }; + } + + if (gdprConsent && gdprConsent.consentString) { + userExt = {consent: gdprConsent.consentString}; + } + + if (userIdAsEids && userIdAsEids.length) { + userExt = userExt || {}; + userExt.eids = [...userIdAsEids]; + } + + if (userExt && Object.keys(userExt).length) { + user = user || {}; + user.ext = userExt; + } + + if (user) { + request.user = user; + } + + const userKeywords = deepAccess(config.getConfig('ortb2.user'), 'keywords') || null; + const siteKeywords = deepAccess(config.getConfig('ortb2.site'), 'keywords') || null; + + if (userKeywords) { + pageKeywords = pageKeywords || {}; + pageKeywords.user = pageKeywords.user || {}; + pageKeywords.user.ortb2 = [ + { + name: 'keywords', + keywords: userKeywords.split(','), + } + ]; + } + if (siteKeywords) { + pageKeywords = pageKeywords || {}; + pageKeywords.site = pageKeywords.site || {}; + pageKeywords.site.ortb2 = [ + { + name: 'keywords', + keywords: siteKeywords.split(','), + } + ]; + } + + if (pageKeywords) { + pageKeywords = reformatKeywords(pageKeywords); + if (pageKeywords) { + request.ext = { + keywords: pageKeywords + }; } } - if (oldFormatBids.length) { - const oldFormatRequests = oldFormatRequest(oldFormatBids, bidderRequest); - if (oldFormatRequests) { - requests.push(oldFormatRequests); + + if (gdprConsent && gdprConsent.gdprApplies) { + request.regs = { + ext: { + gdpr: gdprConsent.gdprApplies ? 1 : 0 + } + } + } + + if (uspConsent) { + if (!request.regs) { + request.regs = {ext: {}}; } + request.regs.ext.us_privacy = uspConsent; } - return requests; + + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(request), + bidsMap + }; }, /** * Unpack the response from the server into a list of bids. * * @param {*} serverResponse A successful response from the server. * @param {*} bidRequest + * @param {*} RendererConst * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { @@ -90,36 +280,39 @@ export const spec = { _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst); }); } - if (errorMessage) utils.logError(errorMessage); + if (errorMessage) logError(errorMessage); return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { if (syncOptions.pixelEnabled) { - return [{ + let params = []; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params.push(`gdpr=${Number(gdprConsent.gdprApplies)}`); + } + if (typeof gdprConsent.consentString === 'string') { + params.push(`gdpr_consent=${gdprConsent.consentString}`); + } + } + if (uspConsent) { + params.push(`us_privacy=${uspConsent}`); + } + const stringParams = params.join('&'); + return { type: 'image', - url: ADAPTER_SYNC_URL - }]; + url: ADAPTER_SYNC_URL + stringParams + }; } } } -function isPopulatedArray(arr) { - return !!(utils.isArray(arr) && arr.length > 0); -} - -function deleteValues(keyPairObj) { - if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { - delete keyPairObj.value; - } -} - function _getBidFromResponse(respItem) { if (!respItem) { - utils.logError(LOG_ERROR_MESS.emptySeatbid); + logError(LOG_ERROR_MESS.emptySeatbid); } else if (!respItem.bid) { - utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); } else if (!respItem.bid[0]) { - utils.logError(LOG_ERROR_MESS.noBid); + logError(LOG_ERROR_MESS.noBid); } return respItem && respItem.bid && respItem.bid[0]; } @@ -128,25 +321,10 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst) { if (!serverBid) return; let errorMessage; if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); - if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + if (!serverBid.adm && !serverBid.nurl) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else { - const { bidsMap, priceType, newFormat } = bidRequest; - let bid; - let slot; - if (newFormat) { - bid = bidsMap[serverBid.impid]; - } else { - const awaitingBids = bidsMap[serverBid.auid]; - if (awaitingBids) { - const sizeId = `${serverBid.w}x${serverBid.h}`; - if (awaitingBids[sizeId]) { - slot = awaitingBids[sizeId][0]; - bid = slot.bids.shift(); - } - } else { - errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; - } - } + const { bidsMap } = bidRequest; + const bid = bidsMap[serverBid.impid]; if (!errorMessage && bid) { const bidResponse = { @@ -156,16 +334,23 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst) { height: serverBid.h, creativeId: serverBid.auid, // bid.bidId, currency: 'USD', - netRevenue: newFormat ? false : priceType !== 'gross', + netRevenue: false, ttl: TIME_TO_LIVE, - dealId: serverBid.dealid + dealId: serverBid.dealid, + meta: { + advertiserDomains: serverBid.adomain ? serverBid.adomain : [] + }, }; if (serverBid.content_type === 'video') { - bidResponse.vastXml = serverBid.adm; + if (serverBid.adm) { + bidResponse.vastXml = serverBid.adm; + bidResponse.adResponse = { + content: bidResponse.vastXml + }; + } else if (serverBid.nurl) { + bidResponse.vastUrl = serverBid.nurl; + } bidResponse.mediaType = VIDEO; - bidResponse.adResponse = { - content: bidResponse.vastXml - }; if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { bidResponse.renderer = createRenderer(bidResponse, { id: bid.bidId, @@ -179,24 +364,9 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst) { bidResponses.push(bidResponse); } - - if (slot && !slot.bids.length) { - slot.parents.forEach(({parent, key, uid}) => { - const index = parent[key].indexOf(slot); - if (index > -1) { - parent[key].splice(index, 1); - } - if (!parent[key].length) { - delete parent[key]; - if (!utils.getKeys(parent).length) { - delete bidsMap[uid]; - } - } - }); - } } if (errorMessage) { - utils.logError(errorMessage); + logError(errorMessage); } } @@ -219,7 +389,7 @@ function createRenderer (bid, rendererParams, RendererConst) { try { rendererInst.setRender(outstreamRender); } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); + logWarn('Prebid Error calling setRender on renderer', err); } return rendererInst; @@ -230,7 +400,7 @@ function createVideoRequest(bid, mediaType) { const size = (playerSize || bid.sizes || [])[0]; if (!size) return; - let result = utils.parseGPTSingleSizeArrayToRtbSize(size); + let result = parseGPTSingleSizeArrayToRtbSize(size); if (mimes) { result.mimes = mimes; @@ -252,8 +422,8 @@ function createBannerRequest(bid, mediaType) { const sizes = mediaType.sizes || bid.sizes; if (!sizes || !sizes.length) return; - let format = sizes.map((size) => utils.parseGPTSingleSizeArrayToRtbSize(size)); - let result = utils.parseGPTSingleSizeArrayToRtbSize(sizes[0]); + let format = sizes.map((size) => parseGPTSingleSizeArrayToRtbSize(size)); + let result = parseGPTSingleSizeArrayToRtbSize(sizes[0]); if (format.length) { result.format = format @@ -261,6 +431,81 @@ 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; + } + }); + 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() })) + }); + } + } +} + +function reformatKeywords(pageKeywords) { + const formatedPageKeywords = {}; + Object.keys(pageKeywords).forEach((name) => { + const keywords = pageKeywords[name]; + if (keywords) { + if (name === 'site' || name === 'user') { + const formatedKeywords = {}; + Object.keys(keywords).forEach((pubName) => { + if (Array.isArray(keywords[pubName])) { + const formatedPublisher = []; + keywords[pubName].forEach((pubItem) => { + if (typeof pubItem === 'object' && pubItem.name) { + const formatedPubItem = { name: pubItem.name, segments: [] }; + Object.keys(pubItem).forEach((key) => { + if (Array.isArray(pubItem[key])) { + pubItem[key].forEach((keyword) => { + if (keyword) { + if (typeof keyword === 'string') { + formatedPubItem.segments.push({ name: key, value: keyword }); + } else if (key === 'segments' && typeof keyword.name === 'string' && typeof keyword.value === 'string') { + formatedPubItem.segments.push(keyword); + } + } + }); + } + }); + if (formatedPubItem.segments.length) { + formatedPublisher.push(formatedPubItem); + } + } + }); + if (formatedPublisher.length) { + formatedKeywords[pubName] = formatedPublisher; + } + } + }); + formatedPageKeywords[name] = formatedKeywords; + } else { + formatedPageKeywords[name] = keywords; + } + } + }); + return Object.keys(formatedPageKeywords).length && formatedPageKeywords; +} + /** * Gets bidfloor * @param {Object} mediaTypes @@ -288,280 +533,4 @@ function _getFloor (mediaTypes, bid) { return floor; } -function newFormatRequest(validBidRequests, bidderRequest) { - if (!validBidRequests.length) { - return null; - } - let pageKeywords = null; - let jwpseg = null; - let content = null; - let schain = null; - let userId = null; - let userIdAsEids = null; - let user = null; - let userExt = null; - let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; - - const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; - const imp = []; - const bidsMap = {}; - - validBidRequests.forEach((bid) => { - if (!bidderRequestId) { - bidderRequestId = bid.bidderRequestId; - } - if (!auctionId) { - auctionId = bid.auctionId; - } - if (!schain) { - schain = bid.schain; - } - if (!userId) { - userId = bid.userId; - } - if (!userIdAsEids) { - userIdAsEids = bid.userIdAsEids; - } - const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd} = bid; - bidsMap[bidId] = bid; - if (!pageKeywords && !utils.isEmpty(keywords)) { - pageKeywords = utils.transformBidderParamKeywords(keywords); - } - const bidFloor = _getFloor(mediaTypes || {}, bid); - const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; - if (jwTargeting) { - if (!jwpseg && jwTargeting.segments) { - jwpseg = jwTargeting.segments; - } - if (!content && jwTargeting.content) { - content = jwTargeting.content; - } - } - let impObj = { - id: bidId, - tagid: uid.toString(), - ext: { - divid: adUnitCode - } - }; - - if (bidFloor) { - impObj.bidfloor = bidFloor; - } - - if (!mediaTypes || mediaTypes[BANNER]) { - const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); - if (banner) { - impObj.banner = banner; - } - } - if (mediaTypes && mediaTypes[VIDEO]) { - const video = createVideoRequest(bid, mediaTypes[VIDEO]); - if (video) { - impObj.video = video; - } - } - - if (impObj.banner || impObj.video) { - imp.push(impObj); - } - }); - - const source = { - tid: auctionId, - ext: { - wrapper: 'Prebid_js', - wrapper_version: '$prebid.version$' - } - }; - - if (schain) { - source.ext.schain = schain; - } - - const bidderTimeout = config.getConfig('bidderTimeout') || timeout; - const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; - - let request = { - id: bidderRequestId, - site: { - page: referer - }, - tmax, - source, - imp - }; - - if (content) { - request.site.content = content; - } - - if (jwpseg && jwpseg.length) { - user = { - data: [{ - name: 'iow_labs_pub_data', - segment: jwpseg.map((seg) => { - return {name: 'jwpseg', value: seg}; - }) - }] - }; - } - - if (gdprConsent && gdprConsent.consentString) { - userExt = {consent: gdprConsent.consentString}; - } - - if (userIdAsEids && userIdAsEids.length) { - userExt = userExt || {}; - userExt.eids = [...userIdAsEids]; - } - - if (userExt && Object.keys(userExt).length) { - user = user || {}; - user.ext = userExt; - } - - if (user) { - request.user = user; - } - - const configKeywords = utils.transformBidderParamKeywords({ - 'user': utils.deepAccess(config.getConfig('ortb2.user'), 'keywords') || null, - 'context': utils.deepAccess(config.getConfig('ortb2.site'), 'keywords') || null - }); - - if (configKeywords.length) { - pageKeywords = (pageKeywords || []).concat(configKeywords); - } - - if (pageKeywords && pageKeywords.length > 0) { - pageKeywords.forEach(deleteValues); - } - - if (pageKeywords) { - request.ext = { - keywords: pageKeywords - }; - } - - if (gdprConsent && gdprConsent.gdprApplies) { - request.regs = { - ext: { - gdpr: gdprConsent.gdprApplies ? 1 : 0 - } - } - } - - if (uspConsent) { - if (!request.regs) { - request.regs = {ext: {}}; - } - request.regs.ext.us_privacy = uspConsent; - } - - return { - method: 'POST', - url: NEW_ENDPOINT_URL, - data: JSON.stringify(request), - newFormat: true, - bidsMap - }; -} - -function oldFormatRequest(validBidRequests, bidderRequest) { - const auids = []; - const bidsMap = {}; - const slotsMapByUid = {}; - const sizeMap = {}; - const bids = validBidRequests || []; - let priceType = 'net'; - let pageKeywords; - let reqId; - - bids.forEach(bid => { - if (bid.params.priceType === 'gross') { - priceType = 'gross'; - } - reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode} = bid; - auids.push(uid); - const sizesId = utils.parseSizesInput(bid.sizes); - - if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { - const keywords = utils.transformBidderParamKeywords(bid.params.keywords); - - if (keywords.length > 0) { - keywords.forEach(deleteValues); - } - pageKeywords = keywords; - } - - if (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; - } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; - } else { - slotsMap[adUnitCode].bids.push(bid); - } - const slot = slotsMap[adUnitCode]; - - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; - } - - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); - } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); - }); - - const payload = { - pt: priceType, - auids: auids.join(','), - sizes: utils.getKeys(sizeMap).join(','), - r: reqId, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' - }; - - if (pageKeywords) { - payload.keywords = JSON.stringify(pageKeywords); - } - - if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; - } - if (bidderRequest.timeout) { - payload.wtimeout = bidderRequest.timeout; - } - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - payload.gdpr_applies = - (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') - ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; - } - if (bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; - } - } - - return { - method: 'GET', - url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), - bidsMap, - priceType - }; -} - registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md index e891df8f161..f29d47eaf36 100644 --- a/modules/trustxBidAdapter.md +++ b/modules/trustxBidAdapter.md @@ -14,53 +14,60 @@ TrustX Bid Adapter supports Banner and Video (instream and outstream). var adUnits = [ { code: 'test-div', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, bids: [ { bidder: "trustx", params: { - uid: '44', - priceType: 'gross' // by default is 'net' + uid: '58851', } } ] },{ code: 'test-div', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90],[300, 250]], + } + }, bids: [ { bidder: "trustx", params: { - uid: 45, - priceType: 'gross', + uid: 58851, keywords: { - brandsafety: ['disaster'], - topic: ['stress', 'fear'] + site: { + publisher: { + name: 'someKeywordsName', + brandsafety: ['disaster'], + topic: ['stress', 'fear'] + } + } } } } ] },{ code: 'test-div', - sizes: [[640, 360]], - mediaTypes: { video: {} }, - bids: [ - { - bidder: "trustx", - params: { - uid: 7697 - } + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 360], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1 } - ] - },{ - code: 'test-div', - sizes: [[300, 250]], + }, bids: [ { bidder: "trustx", params: { - uid: '58851', - useNewFormat: true + uid: 7697 } } ] 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/turktelekomBidAdapter.js b/modules/turktelekomBidAdapter.js deleted file mode 100644 index 852e557290c..00000000000 --- a/modules/turktelekomBidAdapter.js +++ /dev/null @@ -1,261 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'turktelekom'; -const ENDPOINT_URL = 'https://ssp.programattik.com/hb'; -const TIME_TO_LIVE = 360; -const ADAPTER_SYNC_URL = 'https://ssp.programattik.com/sync'; -const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; - -const LOG_ERROR_MESS = { - noAuid: 'Bid from response has no auid parameter - ', - noAdm: 'Bid from response has no adm parameter - ', - noBid: 'Array of bid objects is empty', - noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', - emptyUids: 'Uids should be not empty', - emptySeatbid: 'Seatbid array from response has empty item', - emptyResponse: 'Response is empty', - 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 ], - /** - * 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) { - return !!bid.params.uid; - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests - an array of bids - * @param {bidderRequest} - bidder request object - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests, bidderRequest) { - const auids = []; - const bidsMap = {}; - const slotsMapByUid = {}; - const sizeMap = {}; - const bids = validBidRequests || []; - let priceType = 'net'; - let reqId; - - bids.forEach(bid => { - if (bid.params.priceType === 'gross') { - priceType = 'gross'; - } - reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode} = bid; - auids.push(uid); - const sizesId = utils.parseSizesInput(bid.sizes); - - if (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; - } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; - } else { - slotsMap[adUnitCode].bids.push(bid); - } - const slot = slotsMap[adUnitCode]; - - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; - } - - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); - } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); - }); - - const payload = { - pt: priceType, - auids: auids.join(','), - sizes: utils.getKeys(sizeMap).join(','), - r: reqId, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' - }; - - if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; - } - if (bidderRequest.timeout) { - payload.wtimeout = bidderRequest.timeout; - } - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - payload.gdpr_applies = - (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') - ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; - } - } - - return { - method: 'GET', - url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), - bidsMap: bidsMap, - }; - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @param {*} bidRequest - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { - serverResponse = serverResponse && serverResponse.body; - const bidResponses = []; - const bidsMap = bidRequest.bidsMap; - const priceType = bidRequest.data.pt; - - let errorMessage; - - if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; - else if (serverResponse.seatbid && !serverResponse.seatbid.length) { - errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; - } - - if (!errorMessage && serverResponse.seatbid) { - serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses, RendererConst); - }); - } - if (errorMessage) utils.logError(errorMessage); - return bidResponses; - }, - getUserSyncs: function(syncOptions) { - if (syncOptions.pixelEnabled) { - return [{ - type: 'image', - url: ADAPTER_SYNC_URL - }]; - } - } -} - -function _getBidFromResponse(respItem) { - if (!respItem) { - utils.logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!respItem.bid) { - utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); - } else if (!respItem.bid[0]) { - utils.logError(LOG_ERROR_MESS.noBid); - } - return respItem && respItem.bid && respItem.bid[0]; -} - -function _addBidResponse(serverBid, bidsMap, priceType, bidResponses, RendererConst) { - if (!serverBid) return; - let errorMessage; - if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); - if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); - else { - const awaitingBids = bidsMap[serverBid.auid]; - if (awaitingBids) { - const sizeId = `${serverBid.w}x${serverBid.h}`; - if (awaitingBids[sizeId]) { - const slot = awaitingBids[sizeId][0]; - - const bid = slot.bids.shift(); - const bidResponse = { - requestId: bid.bidId, // bid.bidderRequestId, - bidderCode: spec.code, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid, // bid.bidId, - currency: 'TRY', - netRevenue: priceType !== 'gross', - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid - }; - if (serverBid.content_type === 'video') { - bidResponse.vastXml = serverBid.adm; - bidResponse.mediaType = VIDEO; - bidResponse.adResponse = { - content: bidResponse.vastXml - }; - if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { - bidResponse.renderer = createRenderer(bidResponse, { - id: bid.bidId, - url: RENDERER_URL - }, RendererConst); - } - } else { - bidResponse.ad = serverBid.adm; - bidResponse.mediaType = BANNER; - } - - bidResponses.push(bidResponse); - - if (!slot.bids.length) { - slot.parents.forEach(({parent, key, uid}) => { - const index = parent[key].indexOf(slot); - if (index > -1) { - parent[key].splice(index, 1); - } - if (!parent[key].length) { - delete parent[key]; - if (!utils.getKeys(parent).length) { - delete bidsMap[uid]; - } - } - }); - } - } - } else { - errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; - } - } - if (errorMessage) { - utils.logError(errorMessage); - } -} - -function outstreamRender (bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - targetId: bid.adUnitCode, - adResponse: bid.adResponse - }); - }); -} - -function createRenderer (bid, rendererParams, RendererConst) { - const rendererInst = RendererConst.install({ - id: rendererParams.id, - url: rendererParams.url, - loaded: false - }); - - try { - rendererInst.setRender(outstreamRender); - } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); - } - - return rendererInst; -} - -registerBidder(spec); diff --git a/modules/turktelekomBidAdapter.md b/modules/turktelekomBidAdapter.md deleted file mode 100644 index 360e7f95230..00000000000 --- a/modules/turktelekomBidAdapter.md +++ /dev/null @@ -1,49 +0,0 @@ -# Overview - -Module Name: Türk Telekom Bidder Adapter -Module Type: Bidder Adapter -Maintainer: turktelssp@gmail.com - -# Description - -Module that connects to Türk Telekom demand source to fetch bids. -Türk Telekom Bid Adapter supports Banner and Video (instream and outstream). - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [ - { - bidder: "turktelekom", - params: { - uid: 17, - priceType: 'gross' // by default is 'net' - } - } - ] - },{ - code: 'test-div', - mediaTypes: { - video: { - playerSize: [[640, 360]], - context: 'instream' - } - }, - bids: [ - { - bidder: "turktelekom", - params: { - uid: 19 - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 9b9134a8ef0..8b85f1ebad3 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -1,13 +1,14 @@ +import { generateUUID, _each } from '../src/utils.js'; 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'; -import * as utils from '../src/utils.js'; const storage = getStorageManager(); const COOKIE_NAME = 'ucf_uid'; const VER = 'ADGENT_PREBID-2018011501'; const BIDDER_CODE = 'ucfunnel'; const GVLID = 607; +const CURRENCY = 'USD'; const VIDEO_CONTEXT = { INSTREAM: 0, OUSTREAM: 2 @@ -210,12 +211,41 @@ function getSupplyChain(schain) { return supplyChain; } +function getMediaType(mediaTypes) { + if (mediaTypes != null && mediaTypes.banner) { + return 'banner'; + } else if (mediaTypes != null && mediaTypes.video) { + return 'video'; + } else if (mediaTypes != null && mediaTypes.native) { + return 'native' + } + return 'banner'; +} + +function getFloor(bid, size, mediaTypes) { + if (bid.params.bidfloor) { + return bid.params.bidfloor; + } + if (typeof bid.getFloor === 'function') { + var bidFloor = bid.getFloor({ + currency: CURRENCY, + mediaType: getMediaType(mediaTypes), + size: (size) ? [ size[0], size[1] ] : '*', + }); + if (bidFloor.currency === CURRENCY) { + return bidFloor.floor; + } + } + return undefined; +} + function getRequestData(bid, bidderRequest) { const size = parseSizes(bid); const language = navigator.language; const dnt = (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0; const userIdTdid = (bid.userId && bid.userId.tdid) ? bid.userId.tdid : ''; const supplyChain = getSupplyChain(bid.schain); + const bidFloor = getFloor(bid, size, bid.mediaTypes); // general bid data let bidData = { ver: VER, @@ -225,9 +255,13 @@ function getRequestData(bid, bidderRequest) { dnt: dnt, adid: bid.params.adid, tdid: userIdTdid, - schain: supplyChain, - fp: bid.params.bidfloor + schain: supplyChain }; + + if (bidFloor) { + bidData.fp = bidFloor; + } + addUserId(bidData, bid.userId); try { bidData.host = window.top.location.hostname; @@ -249,7 +283,7 @@ function getRequestData(bid, bidderRequest) { ucfUid = storage.getCookie(COOKIE_NAME); bidData.ucfUid = ucfUid; } else { - ucfUid = utils.generateUUID(); + ucfUid = generateUUID(); bidData.ucfUid = ucfUid; storage.setCookie(COOKIE_NAME, ucfUid); } @@ -300,16 +334,9 @@ function getRequestData(bid, bidderRequest) { } function addUserId(bidData, userId) { - utils._each(userId, (userIdObjectOrValue, userIdProviderKey) => { + bidData['eids'] = ''; + _each(userId, (userIdObjectOrValue, userIdProviderKey) => { switch (userIdProviderKey) { - case 'sharedid': - if (userIdObjectOrValue.id) { - bidData[userIdProviderKey + '_id'] = userIdObjectOrValue.id; - } - if (userIdObjectOrValue.third) { - bidData[userIdProviderKey + '_third'] = userIdObjectOrValue.third; - } - break; case 'haloId': if (userIdObjectOrValue.haloId) { bidData[userIdProviderKey + 'haloId'] = userIdObjectOrValue.haloId; @@ -333,7 +360,21 @@ function addUserId(bidData, userId) { break; case 'uid2': if (userIdObjectOrValue.id) { - bidData['eids'] = userIdProviderKey + ',' + userIdObjectOrValue.id + bidData['eids'] = (bidData['eids'].length > 0) + ? (bidData['eids'] + '!' + userIdProviderKey + ',' + userIdObjectOrValue.id) + : (userIdProviderKey + ',' + userIdObjectOrValue.id); + } + break; + case 'connectid': + if (userIdObjectOrValue) { + bidData['eids'] = (bidData['eids'].length > 0) + ? (bidData['eids'] + '!verizonMediaId,' + userIdObjectOrValue) + : ('verizonMediaId,' + userIdObjectOrValue); + } + break; + case 'flocId': + if (userIdObjectOrValue.id) { + bidData['cid'] = userIdObjectOrValue.id; } break; default: diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 053b57cb76d..c0cd9166784 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -5,7 +5,7 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js' +import { logInfo } from '../src/utils.js'; import {submodule} from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -28,11 +28,11 @@ function getStorage() { const storage = getStorage(); -const logInfo = createLogInfo(LOG_PRE_FIX); +const _logInfo = createLogInfo(LOG_PRE_FIX); function createLogInfo(prefix) { return function (...strings) { - utils.logInfo(prefix + ' ', ...strings); + logInfo(prefix + ' ', ...strings); } } @@ -48,7 +48,7 @@ function encodeId(value) { id: value } result.uid2 = bidIds; - logInfo('Decoded value ' + JSON.stringify(result)); + _logInfo('Decoded value ' + JSON.stringify(result)); return result; } return undefined; @@ -85,9 +85,9 @@ export const uid2IdSubmodule = { * @returns {uid2Id} */ getId(config, consentData) { - logInfo('Creating UID 2.0'); + _logInfo('Creating UID 2.0'); let value = readCookie() || readFromLocalStorage(); - logInfo('The advertising token: ' + value); + _logInfo('The advertising token: ' + value); return {id: value} }, diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index 8368077a627..2ca4de7a555 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { logMessage, flatten, parseSizesInput } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'underdogmedia'; @@ -7,7 +7,7 @@ const UDM_VENDOR_ID = '159'; const prebidVersion = '$prebid.version$'; let USER_SYNCED = false; -utils.logMessage(`Initializing UDM Adapter. PBJS Version: ${prebidVersion} with adapter version: ${UDM_ADAPTER_VERSION} Updated 20191028`); +logMessage(`Initializing UDM Adapter. PBJS Version: ${prebidVersion} with adapter version: ${UDM_ADAPTER_VERSION} Updated 20191028`); // helper function for testing user syncs export function resetUserSync() { @@ -29,7 +29,7 @@ export const spec = { validBidRequests.forEach(bidParam => { let bidParamSizes = bidParam.mediaTypes && bidParam.mediaTypes.banner && bidParam.mediaTypes.banner.sizes ? bidParam.mediaTypes.banner.sizes : bidParam.sizes; - sizes = utils.flatten(sizes, utils.parseSizesInput(bidParamSizes)); + sizes = flatten(sizes, parseSizesInput(bidParamSizes)); siteId = bidParam.params.siteId; }); @@ -99,7 +99,7 @@ export const spec = { var sizeNotFound = true; const bidParamSizes = bidParam.mediaTypes && bidParam.mediaTypes.banner && bidParam.mediaTypes.banner.sizes ? bidParam.mediaTypes.banner.sizes : bidParam.sizes - utils.parseSizesInput(bidParamSizes).forEach(size => { + parseSizesInput(bidParamSizes).forEach(size => { if (size === mid.width + 'x' + mid.height) { sizeNotFound = false; } @@ -120,6 +120,9 @@ export const spec = { currency: 'USD', netRevenue: false, ttl: mid.ttl || 60, + meta: { + advertiserDomains: mid.advertiser_domains || [] + } }; if (bidResponse.cpm <= 0) { diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index 14a765206b6..f86faf3fe4d 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -12,6 +12,20 @@ const FRAME_USER_SYNC = 'https://cdn.undertone.com/js/usersync.html'; const PIXEL_USER_SYNC_1 = 'https://usr.undertone.com/userPixel/syncOne?id=1&of=2'; const PIXEL_USER_SYNC_2 = 'https://usr.undertone.com/userPixel/syncOne?id=2&of=2'; +function getBidFloor(bidRequest, mediaType) { + if (typeof bidRequest.getFloor !== 'function') { + return 0; + } + + const floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' + }); + + return (floor && floor.currency === 'USD' && floor.floor) || 0; +} + function getCanonicalUrl() { try { let doc = window.top.document; @@ -85,18 +99,29 @@ export const spec = { const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); const pageSizeArray = vw == 0 || vh == 0 ? null : [vw, vh]; + const commons = { + 'adapterVersion': '$prebid.version$', + 'uids': validBidRequests[0].userId, + 'pageSize': pageSizeArray + }; + if (validBidRequests[0].schain) { + commons.schain = validBidRequests[0].schain; + } const payload = { 'x-ut-hb-params': [], - 'commons': { - 'adapterVersion': '$prebid.version$', - 'uids': validBidRequests[0].userId, - 'pageSize': pageSizeArray - } + 'commons': commons }; const referer = bidderRequest.refererInfo.referer; + const canonicalUrl = getCanonicalUrl(); + if (referer) { + commons.referrer = referer; + } + if (canonicalUrl) { + commons.canonicalUrl = canonicalUrl; + } const hostname = parseUrl(referer).hostname; let domain = extractDomainFromHost(hostname); - const pageUrl = getCanonicalUrl() || referer; + const pageUrl = canonicalUrl || referer; const pubid = validBidRequests[0].params.publisherId; let reqUrl = `${URL}?pid=${pubid}&domain=${domain}`; @@ -123,6 +148,9 @@ export const spec = { params: bidReq.params }; const videoMediaType = deepAccess(bidReq, 'mediaTypes.video'); + const mediaType = videoMediaType ? VIDEO : BANNER; + bid.mediaType = mediaType; + bid.bidfloor = getBidFloor(bidReq, mediaType); if (videoMediaType) { bid.video = { playerSize: deepAccess(bidReq, 'mediaTypes.video.playerSize') || null, @@ -131,7 +159,6 @@ export const spec = { maxDuration: deepAccess(bidReq, 'params.video.maxDuration') || null, skippable: deepAccess(bidReq, 'params.video.skippable') || null }; - bid.mediaType = 'video'; } payload['x-ut-hb-params'].push(bid); }); diff --git a/modules/undertoneBidAdapter.md b/modules/undertoneBidAdapter.md index 8ac84b77bd8..8e0b234fd7a 100644 --- a/modules/undertoneBidAdapter.md +++ b/modules/undertoneBidAdapter.md @@ -1,9 +1,13 @@ # Overview ``` -Module Name: Example Bidder Adapter +Module Name: Undertone Bidder Adapter Module Type: Bidder Adapter Maintainer: RampProgrammatic@perion.com +gdpr_supported: true +usp_supported: true +schain_supported: true +media_types: video, native ``` # Description diff --git a/modules/unicornBidAdapter.js b/modules/unicornBidAdapter.js index 2d4b5b53966..0209c808979 100644 --- a/modules/unicornBidAdapter.js +++ b/modules/unicornBidAdapter.js @@ -1,14 +1,14 @@ -import * as utils from '../src/utils.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { getStorageManager } from '../src/storageManager.js'; +import { logInfo, deepAccess, generateUUID } from '../src/utils.js'; +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.0'; +const UNICORN_PB_VERSION = '1.1'; /** * Placement ID and Account ID are required. @@ -39,36 +39,30 @@ export const buildRequests = (validBidRequests, bidderRequest) => { * @returns {string} */ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { - utils.logInfo( - '[UNICORN] buildOpenRtbBidRequestPayload.validBidRequests:', - validBidRequests - ); - utils.logInfo( - '[UNICORN] buildOpenRtbBidRequestPayload.bidderRequest:', - bidderRequest - ); + logInfo('[UNICORN] buildOpenRtbBidRequestPayload.validBidRequests:', validBidRequests); + logInfo('[UNICORN] buildOpenRtbBidRequestPayload.bidderRequest:', bidderRequest); const imp = validBidRequests.map(br => { return { id: br.bidId, banner: { format: makeFormat(br.sizes), w: br.sizes[0][0], - h: br.sizes[0][1], + h: br.sizes[0][1] }, - tagid: utils.deepAccess(br, 'params.placementId') || br.adUnitCode, + tagid: deepAccess(br, 'params.placementId') || br.adUnitCode, secure: 1, - bidfloor: parseFloat(utils.deepAccess(br, 'params.bidfloorCpm') || 0) + bidfloor: parseFloat(0) }; }); const request = { id: bidderRequest.auctionId, at: 1, imp, - cur: UNICORN_DEFAULT_CURRENCY, + cur: [UNICORN_DEFAULT_CURRENCY], site: { - id: utils.deepAccess(validBidRequests[0], 'params.mediaId') || '', + id: deepAccess(validBidRequests[0], 'params.mediaId') || '', publisher: { - id: utils.deepAccess(validBidRequests[0], 'params.publisherId') || 0 + id: String(deepAccess(validBidRequests[0], 'params.publisherId') || 0) }, domain: window.location.hostname, page: window.location.href, @@ -81,7 +75,7 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { user: { id: getUid() }, - bcat: utils.deepAccess(validBidRequests[0], 'params.bcat') || [], + bcat: deepAccess(validBidRequests[0], 'params.bcat') || [], source: { ext: { stype: 'prebid_uncn', @@ -90,22 +84,22 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { } }, ext: { - accountId: utils.deepAccess(validBidRequests[0], 'params.accountId') + accountId: deepAccess(validBidRequests[0], 'params.accountId') } }; - utils.logInfo('[UNICORN] OpenRTB Formatted Request:', request); + logInfo('[UNICORN] OpenRTB Formatted Request:', request); return JSON.stringify(request); } const interpretResponse = (serverResponse, request) => { - utils.logInfo('[UNICORN] interpretResponse.serverResponse:', serverResponse); - utils.logInfo('[UNICORN] interpretResponse.request:', request); + logInfo('[UNICORN] interpretResponse.serverResponse:', serverResponse); + logInfo('[UNICORN] interpretResponse.request:', request); const res = serverResponse.body; var bids = [] if (res) { res.seatbid.forEach(sb => { sb.bid.forEach(b => { - bids.push({ + var bid = { requestId: b.impid, cpm: b.price || 0, width: b.w, @@ -115,11 +109,17 @@ const interpretResponse = (serverResponse, request) => { creativeId: b.crid, netRevenue: false, currency: res.cur - }) + } + + if (b.adomain != undefined || b.adomain != null) { + bid.meta = { advertiserDomains: b.adomain }; + } + + bids.push(bid) }) }); } - utils.logInfo('[UNICORN] interpretResponse bids:', bids); + logInfo('[UNICORN] interpretResponse bids:', bids); return bids; }; @@ -132,7 +132,7 @@ const getUid = () => { return JSON.parse(ck)['uid']; } else { const newCk = { - uid: utils.generateUUID() + uid: generateUUID() }; const expireIn = new Date(Date.now() + 24 * 60 * 60 * 10000).toUTCString(); storage.setCookie(UNICORN_PB_COOKIE_KEY, JSON.stringify(newCk), expireIn); @@ -145,7 +145,7 @@ const getUid = () => { * @param {Array} arr */ const makeFormat = arr => arr.map((s) => { - return { w: s[0], h: s[1] }; + return {w: s[0], h: s[1]}; }); export const spec = { @@ -154,7 +154,7 @@ export const spec = { supportedMediaTypes: [BANNER], isBidRequestValid, buildRequests, - interpretResponse, + interpretResponse }; registerBidder(spec); diff --git a/modules/unicornBidAdapter.md b/modules/unicornBidAdapter.md index 5b8c8268bcf..52c11aae3ee 100644 --- a/modules/unicornBidAdapter.md +++ b/modules/unicornBidAdapter.md @@ -22,7 +22,6 @@ Module that connects to UNICORN. bidder: 'unicorn', params: { placementId: 'rectangle-ad-1', // OPTIONAL: If placementId is empty, adunit code will be used as placementId. - bidfloorCpm: 0.2, // OPTIONAL: Floor CPM (JPY) defaults to 0 publisherId: 99999 // OPTIONAL: Account specific publisher id mediaId: "uc" // OPTIONAL: Publisher specific media id accountId: 12345, // REQUIRED: Account ID for charge request diff --git a/modules/unifiedIdSystem.js b/modules/unifiedIdSystem.js index bc033f37992..8ec5fcd3f90 100644 --- a/modules/unifiedIdSystem.js +++ b/modules/unifiedIdSystem.js @@ -5,7 +5,7 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js' +import { logError } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js' @@ -40,7 +40,7 @@ export const unifiedIdSubmodule = { getId(config) { const configParams = (config && config.params) || {}; if (!configParams || (typeof configParams.partner !== 'string' && typeof configParams.url !== 'string')) { - utils.logError('User ID - unifiedId submodule requires either partner or url to be defined'); + logError('User ID - unifiedId submodule requires either partner or url to be defined'); return; } // use protocol relative urls for http or https @@ -54,13 +54,13 @@ export const unifiedIdSubmodule = { try { responseObj = JSON.parse(response); } catch (error) { - utils.logError(error); + logError(error); } } callback(responseObj); }, error: error => { - utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); callback(); } }; diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index 36a156d5c66..99fbe63aeb4 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -1,137 +1,225 @@ -import * as utils from '../src/utils.js' -import { Renderer } from '../src/Renderer.js' -import { registerBidder } from '../src/adapters/bidderFactory.js' -import { VIDEO } from '../src/mediaTypes.js' +import { deepAccess, logError } from '../src/utils.js'; +import {Renderer} from '../src/Renderer.js' +import {registerBidder} from '../src/adapters/bidderFactory.js' +import {VIDEO, BANNER} from '../src/mediaTypes.js' -function configureUniversalTag (exchangeRenderer) { - if (!exchangeRenderer.config) throw new Error('UnrulyBidAdapter: Missing renderer config.') - if (!exchangeRenderer.config.siteId) throw new Error('UnrulyBidAdapter: Missing renderer siteId.') +function configureUniversalTag(exchangeRenderer, requestId) { + if (!exchangeRenderer.config) throw new Error('UnrulyBidAdapter: Missing renderer config.'); + if (!exchangeRenderer.config.siteId) throw new Error('UnrulyBidAdapter: Missing renderer siteId.'); parent.window.unruly = parent.window.unruly || {}; parent.window.unruly['native'] = parent.window.unruly['native'] || {}; parent.window.unruly['native'].siteId = parent.window.unruly['native'].siteId || exchangeRenderer.config.siteId; + parent.window.unruly['native'].adSlotId = requestId; parent.window.unruly['native'].supplyMode = 'prebid'; } -function configureRendererQueue () { +function configureRendererQueue() { parent.window.unruly['native'].prebid = parent.window.unruly['native'].prebid || {}; parent.window.unruly['native'].prebid.uq = parent.window.unruly['native'].prebid.uq || []; } -function notifyRenderer (bidResponseBid) { +function notifyRenderer(bidResponseBid) { parent.window.unruly['native'].prebid.uq.push(['render', bidResponseBid]); } -const serverResponseToBid = (bid, rendererInstance) => ({ - requestId: bid.bidId, - cpm: bid.cpm, - width: bid.width, - height: bid.height, - vastUrl: bid.vastUrl, - netRevenue: true, - creativeId: bid.bidId, - ttl: 360, - meta: { advertiserDomains: bid && bid.adomain ? bid.adomain : [] }, - currency: 'USD', - renderer: rendererInstance, - mediaType: VIDEO -}); - -const buildPrebidResponseAndInstallRenderer = bids => - bids - .filter(serverBid => { - const hasConfig = !!utils.deepAccess(serverBid, 'ext.renderer.config'); - const hasSiteId = !!utils.deepAccess(serverBid, 'ext.renderer.config.siteId'); - - if (!hasConfig) utils.logError(new Error('UnrulyBidAdapter: Missing renderer config.')); - if (!hasSiteId) utils.logError(new Error('UnrulyBidAdapter: Missing renderer siteId.')); - - return hasSiteId - }) - .map(serverBid => { - const exchangeRenderer = utils.deepAccess(serverBid, 'ext.renderer'); - - configureUniversalTag(exchangeRenderer); - configureRendererQueue(); - - const rendererInstance = Renderer.install(Object.assign({}, exchangeRenderer, { callback: () => {} })); - return { rendererInstance, serverBid }; - }) - .map( - ({rendererInstance, serverBid}) => { - const prebidBid = serverResponseToBid(serverBid, rendererInstance); - - const rendererConfig = Object.assign( - {}, - prebidBid, - { - renderer: rendererInstance, - adUnitCode: serverBid.ext.adUnitCode - } - ); - - rendererInstance.setRender(() => { notifyRenderer(rendererConfig) }); - - return prebidBid; +const addBidFloorInfo = (validBid) => { + Object.keys(validBid.mediaTypes).forEach((key) => { + let floor; + if (typeof validBid.getFloor === 'function') { + floor = validBid.getFloor({ + currency: 'USD', + mediaType: key, + size: '*' + }).floor || 0; + } else { + floor = validBid.params.floor || 0; + } + + validBid.mediaTypes[key].floor = floor; + }); +}; + +const RemoveDuplicateSizes = (validBid) => { + let bannerMediaType = deepAccess(validBid, 'mediaTypes.banner'); + if (bannerMediaType) { + let seenSizes = {}; + let newSizesArray = []; + bannerMediaType.sizes.forEach((size) => { + if (!seenSizes[size.toString()]) { + seenSizes[size.toString()] = true; + newSizesArray.push(size); } - ); + }); -export const adapter = { - code: 'unruly', - supportedMediaTypes: [ VIDEO ], - isBidRequestValid: function(bid) { - if (!bid) return false; + bannerMediaType.sizes = newSizesArray; + } +}; - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); +const getRequests = (conf, validBidRequests, bidderRequest) => { + const {bids, bidderRequestId, auctionId, bidderCode, ...bidderRequestData} = bidderRequest; + const invalidBidsCount = bidderRequest.bids.length - validBidRequests.length; + let requestBySiteId = {}; - return bid.mediaType === 'video' || context === 'outstream'; - }, + validBidRequests.forEach((validBid) => { + const currSiteId = validBid.params.siteId; + addBidFloorInfo(validBid); + RemoveDuplicateSizes(validBid); + requestBySiteId[currSiteId] = requestBySiteId[currSiteId] || []; + requestBySiteId[currSiteId].push(validBid); + }); - buildRequests: function(validBidRequests, bidderRequest) { - const url = 'https://targeting.unrulymedia.com/prebid'; - const method = 'POST'; - const data = { - bidRequests: validBidRequests, - bidderRequest - }; - const options = { contentType: 'text/plain' }; + let request = []; - return { - url, - method, - data, - options + Object.keys(requestBySiteId).forEach((key) => { + let data = { + bidderRequest: Object.assign({}, {bids: requestBySiteId[key], invalidBidsCount, ...bidderRequestData}) }; + + request.push(Object.assign({}, {data, ...conf})); + }); + + return request; +}; + +const handleBidResponseByMediaType = (bids) => { + let bidResponses = []; + + bids.forEach((bid) => { + let parsedBidResponse; + let bidMediaType = deepAccess(bid, 'meta.mediaType'); + if (bidMediaType && bidMediaType.toLowerCase() === 'banner') { + bid.mediaType = BANNER; + parsedBidResponse = handleBannerBid(bid); + } else if (bidMediaType && bidMediaType.toLowerCase() === 'video') { + let context = deepAccess(bid, 'meta.videoContext'); + bid.mediaType = VIDEO; + if (context === 'instream') { + parsedBidResponse = handleInStreamBid(bid); + } else if (context === 'outstream') { + parsedBidResponse = handleOutStreamBid(bid); + } + } + + if (parsedBidResponse) { + bidResponses.push(parsedBidResponse); + } + }); + + return bidResponses; +}; + +const handleBannerBid = (bid) => { + if (!bid.ad) { + logError(new Error('UnrulyBidAdapter: Missing ad config.')); + return; + } + + return bid; +}; + +const handleInStreamBid = (bid) => { + if (!(bid.vastUrl || bid.vastXml)) { + logError(new Error('UnrulyBidAdapter: Missing vastUrl or vastXml config.')); + return; + } + + return bid; +}; + +const handleOutStreamBid = (bid) => { + const hasConfig = !!deepAccess(bid, 'ext.renderer.config'); + const hasSiteId = !!deepAccess(bid, 'ext.renderer.config.siteId'); + + if (!hasConfig) { + logError(new Error('UnrulyBidAdapter: Missing renderer config.')); + return; + } + if (!hasSiteId) { + logError(new Error('UnrulyBidAdapter: Missing renderer siteId.')); + return; + } + + const exchangeRenderer = deepAccess(bid, 'ext.renderer'); + + configureUniversalTag(exchangeRenderer, bid.requestId); + configureRendererQueue(); + + const rendererInstance = Renderer.install(Object.assign({}, exchangeRenderer)); + + const rendererConfig = Object.assign( + {}, + bid, + { + renderer: rendererInstance, + adUnitCode: deepAccess(bid, 'ext.adUnitCode') + } + ); + + rendererInstance.setRender(() => { + notifyRenderer(rendererConfig) + }); + + bid.renderer = bid.renderer || rendererInstance; + return bid; +}; + +const isMediaTypesValid = (bid) => { + const mediaTypeVideoData = deepAccess(bid, 'mediaTypes.video'); + const mediaTypeBannerData = deepAccess(bid, 'mediaTypes.banner'); + let isValid = !!(mediaTypeVideoData || mediaTypeBannerData); + if (isValid && mediaTypeVideoData) { + isValid = isVideoMediaTypeValid(mediaTypeVideoData); + } + if (isValid && mediaTypeBannerData) { + isValid = isBannerMediaTypeValid(mediaTypeBannerData); + } + return isValid; +}; + +const isVideoMediaTypeValid = (mediaTypeVideoData) => { + if (!mediaTypeVideoData.context) { + return false; + } + + const supportedContexts = ['outstream', 'instream']; + return supportedContexts.indexOf(mediaTypeVideoData.context) !== -1; +}; + +const isBannerMediaTypeValid = (mediaTypeBannerData) => { + return mediaTypeBannerData.sizes; +}; + +export const adapter = { + code: 'unruly', + supportedMediaTypes: [VIDEO, BANNER], + isBidRequestValid: function (bid) { + let siteId = deepAccess(bid, 'params.siteId'); + let isBidValid = siteId && isMediaTypesValid(bid); + return !!isBidValid; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + let endPoint = 'https://targeting.unrulymedia.com/unruly_prebid'; + if (validBidRequests[0]) { + endPoint = deepAccess(validBidRequests[0], 'params.endpoint') || endPoint; + } + + const url = endPoint; + const method = 'POST'; + const options = {contentType: 'application/json'}; + return getRequests({url, method, options}, validBidRequests, bidderRequest); }, - interpretResponse: function(serverResponse = {}) { + interpretResponse: function (serverResponse = {}) { const serverResponseBody = serverResponse.body; + const noBidsResponse = []; const isInvalidResponse = !serverResponseBody || !serverResponseBody.bids; return isInvalidResponse ? noBidsResponse - : buildPrebidResponseAndInstallRenderer(serverResponseBody.bids); - }, - - getUserSyncs: function(syncOptions, response, gdprConsent) { - let params = ''; - if (gdprConsent && 'gdprApplies' in gdprConsent) { - if (gdprConsent.gdprApplies && typeof gdprConsent.consentString === 'string') { - params += `?gdpr=1&gdpr_consent=${gdprConsent.consentString}`; - } else { - params += `?gdpr=0`; - } - } - - const syncs = [] - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: 'https://video.unrulymedia.com/iframes/third-party-iframes.html' + params - }); - } - return syncs; + : handleBidResponseByMediaType(serverResponseBody.bids); } }; diff --git a/modules/unrulyBidAdapter.md b/modules/unrulyBidAdapter.md index fc3c6c264be..5bfa09bb646 100644 --- a/modules/unrulyBidAdapter.md +++ b/modules/unrulyBidAdapter.md @@ -2,7 +2,7 @@ **Module Name**: Unruly Bid Adapter **Module Type**: Bidder Adapter -**Maintainer**: prodev@unrulymedia.com +**Maintainer**: prebidsupport@unrulygroup.com # Description @@ -11,21 +11,78 @@ Module that connects to UnrulyX for bids. # Test Parameters ```js - const adUnits = [{ - code: 'ad-slot', - sizes: [[728, 90], [300, 250]], - mediaTypes: { - video: { - context: 'outstream' - } - }, - bids: [{ - bidder: 'unruly', - params: { - targetingUUID: '6f15e139-5f18-49a1-b52f-87e5e69ee65e', - siteId: 1081534 - } - } - ] - }]; +const adUnits = + [ + { + "code": "outstream-ad", + "mediaTypes": { + "video": { + "context": "outstream", + "playerSize": [[640, 480]] + } + }, + "bids": [ + { + "bidder": "unruly", + "params": { + "siteId": 1081534 + } + } + ] + }, + { + "code": "unmissable-ad", + "mediaTypes": { + "video": { + "context": "outstream", + "playerSize": [[640, 480]] + } + }, + "bids": [ + { + "bidder": "unruly", + "params": { + "siteId": 1081534, + "featureOverrides": { + "canRunUnmissable": true + } + } + } + ] + }, + { + "code": "banner-ad", + "mediaTypes": { + "banner": { + "sizes": [[300, 250]] + } + }, + "bids": [ + { + "bidder": "unruly", + "params": { + "siteId": 1081534 + } + } + ] + }, + { + "code": "instream-ad", + "mediaTypes": { + "video": { + "context": "instream", + "mimes": ["video/mp4"], + "playerSize": [[640, 480]] + } + }, + "bids": [ + { + "bidder": "unruly", + "params": { + "siteId": 1081534 + } + } + ] + } + ]; ``` diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 4012079ab6e..e78cffdcbac 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -1,16 +1,37 @@ -import * as utils from '../../src/utils.js'; +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 = { // key-name : {config} + // trustpid + 'trustpid': { + source: 'trustpid.com', + atype: 1, + getValue: function (data) { + return data; + }, + }, + // intentIqId 'intentIqId': { source: 'intentiq.com', atype: 1 }, + // naveggId + 'naveggId': { + source: 'navegg.com', + atype: 1 + }, + + // justId + 'justId': { + source: 'justtag.com', + atype: 1 + }, + // pubCommonId 'pubcid': { source: 'pubcid.org', @@ -58,7 +79,7 @@ const USER_IDS_CONFIG = { return null; }, getUidExt: function(parrableId) { - const extendedData = utils.pick(parrableId, [ + const extendedData = pick(parrableId, [ 'ibaOptout', 'ccpaOptout' ]); @@ -134,27 +155,19 @@ const USER_IDS_CONFIG = { atype: 1 }, - // sharedid - 'sharedid': { - source: 'sharedid.org', - atype: 1, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - return (data && data.third) ? { - third: data.third - } : undefined; - } - }, - // zeotapIdPlus 'IDP': { source: 'zeotap.com', atype: 1 }, - // haloId + // hadronId + 'hadronId': { + source: 'audigent.com', + atype: 1 + }, + + // haloId (deprecated in 7.0, use hadronId) 'haloId': { source: 'audigent.com', atype: 1 @@ -189,15 +202,18 @@ const USER_IDS_CONFIG = { source: 'neustar.biz', atype: 1 }, + // MediaWallah OpenLink 'mwOpenLinkId': { source: 'mediawallahscript.com', atype: 1 }, + 'tapadId': { source: 'tapad.com', atype: 1 }, + // Novatiq Snowflake 'novatiq': { getValue: function(data) { @@ -206,6 +222,7 @@ const USER_IDS_CONFIG = { source: 'novatiq.com', atype: 1 }, + 'uid2': { source: 'uidapi.com', atype: 3, @@ -213,15 +230,61 @@ const USER_IDS_CONFIG = { return data.id; } }, + + // Akamai Data Activation Platform (DAP) + 'dapId': { + source: 'akamai.com', + atype: 1 + }, + 'deepintentId': { source: 'deepintent.com', atype: 3 }, + // Admixer Id 'admixerId': { source: 'admixer.net', atype: 3 - } + }, + + // Adtelligent Id + 'adtelligentId': { + source: 'adtelligent.com', + atype: 3 + }, + + amxId: { + source: 'amxrtb.com', + atype: 1, + }, + + 'publinkId': { + source: 'epsilon.com', + atype: 3 + }, + + 'kpuid': { + source: 'kpuid.com', + atype: 3 + }, + + 'imuid': { + source: 'intimatemerger.com', + atype: 1 + }, + + // Yahoo ConnectID + 'connectId': { + source: 'yahoo.com', + atype: 3 + }, + + // Adquery ID + 'qid': { + source: 'adquery.io', + atype: 1 + }, }; // this function will create an eid object for the given UserId sub-module @@ -230,11 +293,11 @@ function createEidObject(userIdData, subModuleKey) { if (conf && userIdData) { let eid = {}; eid.source = conf['source']; - const value = utils.isFn(conf['getValue']) ? conf['getValue'](userIdData) : userIdData; - if (utils.isStr(value)) { + const value = isFn(conf['getValue']) ? conf['getValue'](userIdData) : userIdData; + if (isStr(value)) { const uid = { id: value, atype: conf['atype'] }; // getUidExt - if (utils.isFn(conf['getUidExt'])) { + if (isFn(conf['getUidExt'])) { const uidExt = conf['getUidExt'](userIdData); if (uidExt) { uid.ext = uidExt; @@ -242,7 +305,7 @@ function createEidObject(userIdData, subModuleKey) { } eid.uids = [uid]; // getEidExt - if (utils.isFn(conf['getEidExt'])) { + if (isFn(conf['getEidExt'])) { const eidExt = conf['getEidExt'](userIdData); if (eidExt) { eid.ext = eidExt; @@ -279,11 +342,11 @@ export function createEidsArray(bidRequestUserId) { */ export function buildEidPermissions(submodules) { let eidPermissions = []; - submodules.filter(i => utils.isPlainObject(i.idObj) && Object.keys(i.idObj).length) + submodules.filter(i => isPlainObject(i.idObj) && Object.keys(i.idObj).length) .forEach(i => { Object.keys(i.idObj).forEach(key => { - if (utils.deepAccess(i, 'config.bidders') && Array.isArray(i.config.bidders) && - utils.deepAccess(USER_IDS_CONFIG, key + '.source')) { + if (deepAccess(i, 'config.bidders') && Array.isArray(i.config.bidders) && + deepAccess(USER_IDS_CONFIG, key + '.source')) { eidPermissions.push( { source: USER_IDS_CONFIG[key].source, diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 32aa6b3c0d9..4c516d5441c 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -2,6 +2,14 @@ ``` userIdAsEids = [ + { + source: 'trustpid.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { source: 'pubcid.org', uids: [{ @@ -21,6 +29,22 @@ userIdAsEids = [ }] }, + { + source: 'navegg.com', + uids: [{ + id: 'naveggId', + atype: 1 + }] + }, + + { + source: 'justtag.com', + uids: [{ + id: 'justId', + atype: 1 + }] + }, + { source: 'neustar.biz', uids: [{ @@ -108,17 +132,6 @@ userIdAsEids = [ }] }, - { - source: 'sharedid.org', - uids: [{ - id: 'some-random-id-value', - atype: 1, - ext: { - third: 'some-random-id-value' - } - }] - }, - { source: 'zeotap.com', uids: [{ @@ -186,6 +199,13 @@ userIdAsEids = [ atype: 3 }] }, + { + source: 'akamai.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, { source: 'admixer.net', uids: [{ @@ -199,6 +219,20 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 3 }] + }, + { + source: 'kpuid.com', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + }, + { + source: 'yahoo.com', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] } ] ``` diff --git a/modules/userId/index.js b/modules/userId/index.js index 199934f4cdb..42fff2cd16c 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -133,14 +133,16 @@ import find from 'core-js-pure/features/array/find.js'; import { config } from '../../src/config.js'; import events from '../../src/events.js'; -import * as utils from '../../src/utils.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 {getPrebidInternal} from '../../src/utils.js'; +import { + getPrebidInternal, isPlainObject, logError, isArray, cyrb53Hash, deepAccess, timestamp, delayExecution, logInfo, isFn, + logWarn, isEmptyStr, isNumber, isGptPubadsDefined +} from '../../src/utils.js'; import includes from 'core-js-pure/features/array/includes.js'; const MODULE_NAME = 'User ID'; @@ -182,6 +184,9 @@ export let syncDelay; /** @type {(number|undefined)} */ export let auctionDelay; +/** @type {(string|undefined)} */ +let ppidSource; + /** @param {Submodule[]} submodules */ export function setSubmoduleRegistry(submodules) { submoduleRegistry = submodules; @@ -199,7 +204,7 @@ export function setStoredValue(submodule, value) { const domainOverride = (typeof submodule.submodule.domainOverride === 'function') ? submodule.submodule.domainOverride() : null; try { - const valueStr = utils.isPlainObject(value) ? JSON.stringify(value) : value; + const valueStr = isPlainObject(value) ? JSON.stringify(value) : value; const expiresStr = (new Date(Date.now() + (storage.expires * (60 * 60 * 24 * 1000)))).toUTCString(); if (storage.type === COOKIE) { coreStorage.setCookie(storage.name, valueStr, expiresStr, 'Lax', domainOverride); @@ -214,13 +219,13 @@ export function setStoredValue(submodule, value) { } } } catch (error) { - utils.logError(error); + logError(error); } } function setPrebidServerEidPermissions(initializedSubmodules) { let setEidPermissions = getPrebidInternal().setEidPermissions; - if (typeof setEidPermissions === 'function' && utils.isArray(initializedSubmodules)) { + if (typeof setEidPermissions === 'function' && isArray(initializedSubmodules)) { setEidPermissions(buildEidPermissions(initializedSubmodules)); } } @@ -253,7 +258,7 @@ function getStoredValue(storage, key = undefined) { storedValue = JSON.parse(storedValue); } } catch (e) { - utils.logError(e); + logError(e); } return storedValue; } @@ -277,7 +282,8 @@ function makeStoredConsentDataHash(consentData) { storedConsentData.gdprApplies = consentData.gdprApplies; storedConsentData.apiVersion = consentData.apiVersion; } - return utils.cyrb53Hash(JSON.stringify(storedConsentData)); + + return cyrb53Hash(JSON.stringify(storedConsentData)); } /** @@ -289,7 +295,7 @@ export function setStoredConsentData(consentData) { const expiresStr = (new Date(Date.now() + (CONSENT_DATA_COOKIE_STORAGE_CONFIG.expires * (60 * 60 * 24 * 1000)))).toUTCString(); coreStorage.setCookie(CONSENT_DATA_COOKIE_STORAGE_CONFIG.name, makeStoredConsentDataHash(consentData), expiresStr, 'Lax'); } catch (error) { - utils.logError(error); + logError(error); } } @@ -301,22 +307,22 @@ function getStoredConsentData() { try { return coreStorage.getCookie(CONSENT_DATA_COOKIE_STORAGE_CONFIG.name); } catch (e) { - utils.logError(e); + logError(e); } } /** - * test if the consent object stored locally matches the current consent data. - * if there is nothing in storage, return true and we'll do an actual comparison next time. - * this way, we don't force a refresh for every user when this code rolls out + * test if the consent object stored locally matches the current consent data. if they + * don't match or there is nothing stored locally, it means a refresh of the user id + * submodule is needed * @param storedConsentData * @param consentData * @returns {boolean} */ function storedConsentDataMatchesConsentData(storedConsentData, consentData) { return ( - typeof storedConsentData === 'undefined' || - storedConsentData === null || + typeof storedConsentData !== 'undefined' && + storedConsentData !== null && storedConsentData === makeStoredConsentDataHash(consentData) ); } @@ -331,10 +337,10 @@ function hasGDPRConsent(consentData) { if (!consentData.consentString) { return false; } - if (consentData.apiVersion === 1 && utils.deepAccess(consentData, 'vendorData.purposeConsents.1') === false) { + if (consentData.apiVersion === 1 && deepAccess(consentData, 'vendorData.purposeConsents.1') === false) { return false; } - if (consentData.apiVersion === 2 && utils.deepAccess(consentData, 'vendorData.purpose.consents.1') === false) { + if (consentData.apiVersion === 2 && deepAccess(consentData, 'vendorData.purpose.consents.1') === false) { return false; } } @@ -362,7 +368,7 @@ export function findRootDomain(fullDomain = window.location.hostname) { const TEST_COOKIE_VALUE = 'writeable'; do { rootDomain = domainParts.slice(startIndex).join('.'); - let expirationDate = new Date(utils.timestamp() + 10 * 1000).toUTCString(); + let expirationDate = new Date(timestamp() + 10 * 1000).toUTCString(); // Write a test cookie coreStorage.setCookie( @@ -402,7 +408,7 @@ export function findRootDomain(fullDomain = window.location.hostname) { function processSubmoduleCallbacks(submodules, cb) { let done = () => {}; if (cb) { - done = utils.delayExecution(() => { + done = delayExecution(() => { clearTimeout(timeoutID); cb(); }, submodules.length); @@ -417,7 +423,7 @@ function processSubmoduleCallbacks(submodules, cb) { // cache decoded value (this is copied to every adUnit bid) submodule.idObj = submodule.submodule.decode(idObj, submodule.config); } else { - utils.logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); + logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); } done(); }); @@ -435,7 +441,7 @@ function getCombinedSubmoduleIds(submodules) { if (!Array.isArray(submodules) || !submodules.length) { return {}; } - const combinedSubmoduleIds = submodules.filter(i => utils.isPlainObject(i.idObj) && Object.keys(i.idObj).length).reduce((carry, i) => { + const combinedSubmoduleIds = submodules.filter(i => isPlainObject(i.idObj) && Object.keys(i.idObj).length).reduce((carry, i) => { Object.keys(i.idObj).forEach(key => { carry[key] = i.idObj[key]; }); @@ -455,8 +461,8 @@ function getCombinedSubmoduleIdsForBidder(submodules, bidder) { return {}; } return submodules - .filter(i => !i.config.bidders || !utils.isArray(i.config.bidders) || includes(i.config.bidders, bidder)) - .filter(i => utils.isPlainObject(i.idObj) && Object.keys(i.idObj).length) + .filter(i => !i.config.bidders || !isArray(i.config.bidders) || includes(i.config.bidders, bidder)) + .filter(i => isPlainObject(i.idObj) && Object.keys(i.idObj).length) .reduce((carry, i) => { Object.keys(i.idObj).forEach(key => { carry[key] = i.idObj[key]; @@ -474,7 +480,7 @@ function addIdDataToAdUnitBids(adUnits, submodules) { return; } adUnits.forEach(adUnit => { - if (adUnit.bids && utils.isArray(adUnit.bids)) { + if (adUnit.bids && isArray(adUnit.bids)) { adUnit.bids.forEach(bid => { const combinedSubmoduleIds = getCombinedSubmoduleIdsForBidder(submodules, bid.bidder); if (Object.keys(combinedSubmoduleIds).length) { @@ -499,7 +505,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { if (initializedSubmodules.length) { setPrebidServerEidPermissions(initializedSubmodules); // list of submodules that have callbacks that need to be executed - const submodulesWithCallbacks = initializedSubmodules.filter(item => utils.isFn(item.callback)); + const submodulesWithCallbacks = initializedSubmodules.filter(item => isFn(item.callback)); if (submodulesWithCallbacks.length) { if (continueAuction && auctionDelay > 0) { @@ -512,7 +518,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { continueAuction(); } } - utils.logInfo(`${MODULE_NAME} - auction delayed by ${auctionDelay} at most to fetch ids`); + logInfo(`${MODULE_NAME} - auction delayed by ${auctionDelay} at most to fetch ids`); timeoutID = setTimeout(continueCallback, auctionDelay); processSubmoduleCallbacks(submodulesWithCallbacks, continueCallback); @@ -554,6 +560,26 @@ export function requestBidsHook(fn, reqBidsConfigObj) { initializeSubmodulesAndExecuteCallbacks(function () { // 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); + }); + } + } else { + logWarn(`User ID - Googletag Publisher Provided ID for ${ppidSource} is not between 32 and 150 characters - ${ppidValue}`); + } + } + // calling fn allows prebid to continue processing fn.call(this, reqBidsConfigObj); }); @@ -595,7 +621,7 @@ function refreshUserIds(options, callback) { // gdpr consent with purpose one is required, otherwise exit immediately let {userIdModules, hasValidated} = validateGdprEnforcement(submodules, consentData); if (!hasValidated && !hasGDPRConsent(consentData)) { - utils.logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); + logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); return; } @@ -610,10 +636,15 @@ function refreshUserIds(options, callback) { continue; } - utils.logInfo(`${MODULE_NAME} - refreshing ${submodule.submodule.name}`); + logInfo(`${MODULE_NAME} - refreshing ${submodule.submodule.name}`); populateSubmoduleId(submodule, consentData, storedConsentData, true); + updateInitializedSubmodules(submodule); + + if (initializedSubmodules.length) { + setPrebidServerEidPermissions(initializedSubmodules); + } - if (utils.isFn(submodule.callback)) { + if (isFn(submodule.callback)) { callbackSubmodules.push(submodule); } } @@ -657,7 +688,7 @@ function populateSubmoduleId(submodule, consentData, storedConsentData, forceRef response = submodule.submodule.extendId(submodule.config, consentData, storedId); } - if (utils.isPlainObject(response)) { + if (isPlainObject(response)) { if (response.id) { // A getId/extendId result assumed to be valid user id data, which should be saved to users local storage or cookies setStoredValue(submodule, response.id); @@ -679,7 +710,7 @@ function populateSubmoduleId(submodule, consentData, storedConsentData, forceRef submodule.idObj = submodule.config.value; } else { const response = submodule.submodule.getId(submodule.config, consentData, undefined); - if (utils.isPlainObject(response)) { + if (isPlainObject(response)) { if (typeof response.callback === 'function') { submodule.callback = response.callback; } if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config); } } @@ -695,7 +726,7 @@ function initSubmodules(submodules, consentData) { // gdpr consent with purpose one is required, otherwise exit immediately let { userIdModules, hasValidated } = validateGdprEnforcement(submodules, consentData); if (!hasValidated && !hasGDPRConsent(consentData)) { - utils.logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); + logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); return []; } @@ -710,6 +741,21 @@ function initSubmodules(submodules, consentData) { }, []); } +function updateInitializedSubmodules(submodule) { + let updated = false; + for (let i = 0; i < initializedSubmodules.length; i++) { + if (submodule.config.name.toLowerCase() === initializedSubmodules[i].config.name.toLowerCase()) { + updated = true; + initializedSubmodules[i] = submodule; + break; + } + } + + if (!updated) { + initializedSubmodules.push(submodule); + } +} + /** * list of submodule configurations with valid 'storage' or 'value' obj definitions * * storage config: contains values for storing/retrieving User ID data in browser storage @@ -725,17 +771,17 @@ function getValidSubmoduleConfigs(configRegistry, submoduleRegistry, activeStora } return configRegistry.reduce((carry, config) => { // every submodule config obj must contain a valid 'name' - if (!config || utils.isEmptyStr(config.name)) { + if (!config || isEmptyStr(config.name)) { return carry; } // Validate storage config contains 'type' and 'name' properties with non-empty string values // 'type' must be a value currently enabled in the browser if (config.storage && - !utils.isEmptyStr(config.storage.type) && - !utils.isEmptyStr(config.storage.name) && + !isEmptyStr(config.storage.type) && + !isEmptyStr(config.storage.name) && activeStorageTypes.indexOf(config.storage.type) !== -1) { carry.push(config); - } else if (utils.isPlainObject(config.value)) { + } else if (isPlainObject(config.value)) { carry.push(config); } else if (!config.storage && !config.value) { carry.push(config); @@ -757,7 +803,8 @@ function updateSubmodules() { // find submodule and the matching configuration, if found create and append a SubmoduleContainer submodules = addedSubmodules.map(i => { - const submoduleConfig = find(configs, j => j.name && j.name.toLowerCase() === i.name.toLowerCase()); + 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; i.findRootDomain = findRootDomain; return submoduleConfig ? { @@ -771,7 +818,7 @@ function updateSubmodules() { if (!addedUserIdHook && submodules.length) { // priority value 40 will load after consentManagement with a priority of 50 getGlobal().requestBids.before(requestBidsHook, 40); - utils.logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); + logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); addedUserIdHook = true; } } @@ -793,6 +840,7 @@ export function attachIdSystem(submodule) { * @param {{getConfig:function}} config */ export function init(config) { + ppidSource = undefined; submodules = []; configRegistry = []; addedUserIdHook = false; @@ -806,22 +854,23 @@ export function init(config) { // exit immediately if opt out cookie or local storage keys exists. if (validStorageTypes.indexOf(COOKIE) !== -1 && coreStorage.getCookie(PBJS_USER_ID_OPTOUT_NAME)) { - utils.logInfo(`${MODULE_NAME} - opt-out cookie found, exit module`); + logInfo(`${MODULE_NAME} - opt-out cookie found, exit module`); return; } if (validStorageTypes.indexOf(LOCAL_STORAGE) !== -1 && coreStorage.getDataFromLocalStorage(PBJS_USER_ID_OPTOUT_NAME)) { - utils.logInfo(`${MODULE_NAME} - opt-out localStorage found, exit module`); + logInfo(`${MODULE_NAME} - opt-out localStorage found, exit module`); return; } // listen for config userSyncs to be set - config.getConfig(conf => { + config.getConfig('userSync', conf => { // Note: support for 'usersync' was dropped as part of Prebid.js 4.0 const userSync = conf.userSync; + ppidSource = userSync.ppid; if (userSync && userSync.userIds) { configRegistry = userSync.userIds; - syncDelay = utils.isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY; - auctionDelay = utils.isNumber(userSync.auctionDelay) ? userSync.auctionDelay : NO_AUCTION_DELAY; + syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY; + auctionDelay = isNumber(userSync.auctionDelay) ? userSync.auctionDelay : NO_AUCTION_DELAY; updateSubmodules(); } }); diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 5d78a447572..44ed7003f0c 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -1,6 +1,7 @@ ## User ID Example Configuration Example showing `cookie` storage for user id data for each of the submodules + ``` pbjs.setConfig({ userSync: { @@ -36,7 +37,7 @@ pbjs.setConfig({ name: "id5Id", params: { partner: 173, // Set your real ID5 partner ID here for production, please ask for one at https://id5.io/universal-id - pd: "some-pd-string" // See https://wiki.id5.io/x/BIAZ for details + pd: "some-pd-string" // See https://support.id5.io/portal/en/kb/articles/passing-partner-data-to-id5 for details }, storage: { type: "html5", // ID5 requires html5 @@ -50,7 +51,17 @@ pbjs.setConfig({ // Replace partner with comma-separated (if more than one) Parrable Partner Client ID(s) for Parrable-aware bid adapters in use partner: "30182847-e426-4ff9-b2b5-9ca1324ea09b" } - }, { + },{ + name: 'akamaiDAPId', + params: { + apiHostname: '', + domain: 'your-domain.com', + type: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', + identity: ‘your@email.com’ | ‘6175551234' | ..., + apiVersion: 'v1' | 'x1', + attributes: '{ "cohorts": [ "3:14400", "5:14400", "7:0" ],"first_name": "...","last_name": "..." }' + } + },{ name: 'identityLink', params: { pid: '999', // Set your real identityLink placement ID here @@ -71,16 +82,6 @@ pbjs.setConfig({ name: '_li_pbid', expires: 60 } - }, { - name: 'sharedId', - params: { - syncTime: 60 // in seconds, default is 24 hours - }, - storage: { - type: 'cookie', - name: 'sharedid', - expires: 28 - } }, { name: 'criteo', storage: { // It is best not to specify this parameter since the module needs to be called as many times as possible @@ -101,13 +102,14 @@ pbjs.setConfig({ vendor:'sdfg', sv_cid:'dfg', sv_pubid:'xcv', - sv_domain:'zxv' + sv_domain:'zxv', + refreshInSeconds: 10 // Refreshes the id based on this configuration, else by default every 7 days }, storage: { type: "cookie", name: "merkleId", expires: 30 - } + } },{ name: 'uid2' } @@ -126,9 +128,19 @@ pbjs.setConfig({ },{ name: 'flocId', params: { - token: "Registered token or default sharedid.org token" // Default sharedid.org token: "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9" + token: "Registered token or default sharedid.org token" // Default sharedid.org token: "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9" } - }], + },{ + name: "kpuid", + params:{ + accountid: 124 // example of account id + }, + storage: { + type: "cookie", + name: "knssoId", + expires: 30 + }, + ], syncDelay: 5000, auctionDelay: 1000 } @@ -136,10 +148,23 @@ pbjs.setConfig({ ``` Example showing `localStorage` for user id data for some submodules + ``` pbjs.setConfig({ userSync: { - userIds: [{ + userIds: [ + { + name: 'trustpid', + params: { + maxDelayTime: 2500 + }, + bidders: ['adform'], + storage: { + type: 'html5', + name: 'trustpid', + expires: 60 + } + }, { name: "unifiedId", params: { partner: "prebid", @@ -192,7 +217,7 @@ pbjs.setConfig({ name: 'id5Id', params: { partner: 173, // Set your real ID5 partner ID here for production, please ask for one at https://id5.io/universal-id - pd: 'some-pd-string' // See https://wiki.id5.io/x/BIAZ for details + pd: 'some-pd-string' // See https://support.id5.io/portal/en/kb/articles/passing-partner-data-to-id5 for details }, storage: { type: 'html5', @@ -218,13 +243,14 @@ pbjs.setConfig({ vendor:'sdfg', sv_cid:'dfg', sv_pubid:'xcv', - sv_domain:'zxv' + sv_domain:'zxv', + refreshInSeconds: 10 // Refreshes the id based on this configuration, else by default every 7 days }, storage: { type: "html5", name: "merkleId", expires: 30 - } + } }, { name: 'admixerId', params: { @@ -256,6 +282,43 @@ pbjs.setConfig({ name: "_dpes_id", expires: 90 } + },{ + name: "kpuid", + params:{ + accountid: 124 // example of account id + }, + storage: { + type: "html5", + name: "knssoId", + expires: 30 + }, + } + }, + { + name: 'imuid', + params: { + cid: 5126 // Set your Intimate Merger Customer ID here for production + } + }, + { + name: 'connectId', + params: { + pixelId: 58776, + he: '0bef996248d63cea1529cb86de31e9547a712d9f380146e98bbd39beec70355a' + }, + storage: { + name: 'connectId', + type: 'html5', + expires: 15 + } + } + { + name: "qid", + storage: { + type: "html5", + name: "qid", + expires: 365 + } }], syncDelay: 5000 } @@ -263,6 +326,7 @@ pbjs.setConfig({ ``` Example showing how to configure a `value` object to pass directly to bid adapters + ``` pbjs.setConfig({ userSync: { @@ -287,6 +351,9 @@ pbjs.setConfig({ { name: "novatiq", value: { "snowflake": "81b001ec-8914-488c-a96e-8c220d4ee08895ef" } + }, + { + name: 'naveggId', }], syncDelay: 5000 } diff --git a/modules/validationFpdModule/config.js b/modules/validationFpdModule/config.js new file mode 100644 index 00000000000..c87265fa1df --- /dev/null +++ b/modules/validationFpdModule/config.js @@ -0,0 +1,149 @@ +/** + * Data type map + */ +const TYPES = { + string: 'string', + object: 'object', + number: 'number', +}; + +/** + * Template to define what ortb2 attributes should be validated + * Accepted fields: + * -- invalid - {Boolean} if true, field is not valid + * -- type - {String} valid data type of field + * -- isArray - {Boolean} if true, field must be an array + * -- childType - {String} used in conjuction with isArray: true, defines valid type of array indices + * -- children - {Object} defines child properties needed to be validated (used only if type: object) + * -- required - {Array} array of strings defining any required properties for object (used only if type: object) + * -- optoutApplies - {Boolean} if true, optout logic will filter if applicable (currently only applies to user object) + */ +export const ORTB_MAP = { + imp: { + invalid: true + }, + cur: { + type: TYPES.object, + isArray: true, + childType: TYPES.string + }, + device: { + type: TYPES.object, + children: { + w: { type: TYPES.number }, + h: { type: TYPES.number } + } + }, + site: { + type: TYPES.object, + children: { + name: { type: TYPES.string }, + domain: { type: TYPES.string }, + page: { type: TYPES.string }, + ref: { type: TYPES.string }, + keywords: { type: TYPES.string }, + search: { type: TYPES.string }, + cat: { + type: TYPES.object, + isArray: true, + childType: TYPES.string + }, + sectioncat: { + type: TYPES.object, + isArray: true, + childType: TYPES.string + }, + pagecat: { + type: TYPES.object, + isArray: true, + childType: TYPES.string + }, + content: { + type: TYPES.object, + isArray: false, + children: { + data: { + type: TYPES.object, + isArray: true, + childType: TYPES.object, + required: ['name', 'segment'], + children: { + segment: { + type: TYPES.object, + isArray: true, + childType: TYPES.object, + required: ['id'], + children: { + id: { type: TYPES.string } + } + }, + name: { type: TYPES.string }, + ext: { type: TYPES.object }, + } + } + } + }, + publisher: { + type: TYPES.object, + isArray: false, + children: { + id: { type: TYPES.string }, + name: { type: TYPES.string }, + cat: { + type: TYPES.object, + isArray: true, + childType: TYPES.string + }, + domain: { type: TYPES.string }, + ext: { + type: TYPES.object, + isArray: false + } + } + }, + } + }, + user: { + type: TYPES.object, + children: { + yob: { + type: TYPES.number, + optoutApplies: true + }, + gender: { + type: TYPES.string, + optoutApplies: true + }, + keywords: { type: TYPES.string }, + data: { + type: TYPES.object, + isArray: true, + childType: TYPES.object, + required: ['name', 'segment'], + children: { + segment: { + type: TYPES.object, + isArray: true, + childType: TYPES.object, + required: ['id'], + children: { + id: { type: TYPES.string } + } + }, + name: { type: TYPES.string }, + ext: { type: TYPES.object }, + } + } + } + }, + bcat: { + type: TYPES.object, + isArray: true, + childType: TYPES.string + }, + badv: { + type: TYPES.object, + isArray: true, + childType: TYPES.string + } +} diff --git a/modules/validationFpdModule/index.js b/modules/validationFpdModule/index.js new file mode 100644 index 00000000000..2db170c1bd1 --- /dev/null +++ b/modules/validationFpdModule/index.js @@ -0,0 +1,233 @@ +/** + * This module sets default values and validates ortb2 first part data + * @module modules/firstPartyData + */ +import { config } from '../../src/config.js'; +import { isEmpty, isNumber, logWarn, deepAccess } from '../../src/utils.js'; +import { ORTB_MAP } from './config.js'; +import { submodule } from '../../src/hook.js'; +import { getStorageManager } from '../../src/storageManager.js'; + +const STORAGE = getStorageManager(); +let optout; + +/** + * Check if data passed is empty + * @param {*} value to test against + * @returns {Boolean} is value empty + */ +function isEmptyData(data) { + let check = true; + + if (typeof data === 'object' && !isEmpty(data)) { + check = false; + } else if (typeof data !== 'object' && (isNumber(data) || data)) { + check = false; + } + + return check; +} + +/** + * Check if required keys exist in data object + * @param {Object} data object + * @param {Array} array of required keys + * @param {String} object path (for printing warning) + * @param {Number} index of object value in the data array (for printing warning) + * @returns {Boolean} is requirements fulfilled + */ +function getRequiredData(obj, required, parent, i) { + let check = true; + + required.forEach(key => { + if (!obj[key] || isEmptyData(obj[key])) { + check = false; + logWarn(`Filtered ${parent}[] value at index ${i} in ortb2 data: missing required property ${key}`); + } + }); + + return check; +} + +/** + * Check if data type is valid + * @param {*} value to test against + * @param {Object} object containing type definition and if should be array bool + * @returns {Boolean} is type fulfilled + */ +function typeValidation(data, mapping) { + let check = false; + + switch (mapping.type) { + case 'string': + if (typeof data === 'string') check = true; + break; + case 'number': + if (typeof data === 'number' && isFinite(data)) check = true; + break; + case 'object': + if (typeof data === 'object') { + if ((Array.isArray(data) && mapping.isArray) || (!Array.isArray(data) && !mapping.isArray)) check = true; + } + break; + } + + return check; +} + +/** + * Validates ortb2 data arrays and filters out invalid data + * @param {Array} ortb2 data array + * @param {Object} object defining child type and if array + * @param {String} config path of data array + * @param {String} parent path for logging warnings + * @returns {Array} validated/filtered data + */ +export function filterArrayData(arr, child, path, parent) { + arr = arr.filter((index, i) => { + let check = typeValidation(index, {type: child.type, isArray: child.isArray}); + + if (check && Array.isArray(index) === Boolean(child.isArray)) { + return true; + } + + logWarn(`Filtered ${parent}[] value at index ${i} in ortb2 data: expected type ${child.type}`); + }).filter((index, i) => { + let requiredCheck = true; + let mapping = deepAccess(ORTB_MAP, path); + + if (mapping && mapping.required) requiredCheck = getRequiredData(index, mapping.required, parent, i); + + if (requiredCheck) return true; + }).reduce((result, value, i) => { + let typeBool = false; + let mapping = deepAccess(ORTB_MAP, path); + + switch (child.type) { + case 'string': + result.push(value); + typeBool = true; + break; + case 'object': + if (mapping && mapping.children) { + let validObject = validateFpd(value, path + '.children.', parent + '.'); + if (Object.keys(validObject).length) { + let requiredCheck = getRequiredData(validObject, mapping.required, parent, i); + + if (requiredCheck) { + result.push(validObject); + typeBool = true; + } + } + } else { + result.push(value); + typeBool = true; + } + break; + } + + if (!typeBool) logWarn(`Filtered ${parent}[] value at index ${i} in ortb2 data: expected type ${child.type}`); + + return result; + }, []); + + return arr; +} + +/** + * Validates ortb2 object and filters out invalid data + * @param {Object} ortb2 object + * @param {String} config path of data array + * @param {String} parent path for logging warnings + * @returns {Object} validated/filtered data + */ +export function validateFpd(fpd, path = '', parent = '') { + if (!fpd) return {}; + + // Filter out imp property if exists + let validObject = Object.assign({}, Object.keys(fpd).filter(key => { + let mapping = deepAccess(ORTB_MAP, path + key); + + if (!mapping || !mapping.invalid) return key; + + logWarn(`Filtered ${parent}${key} property in ortb2 data: invalid property`); + }).filter(key => { + let mapping = deepAccess(ORTB_MAP, path + key); + // let typeBool = false; + let typeBool = (mapping) ? typeValidation(fpd[key], {type: mapping.type, isArray: mapping.isArray}) : true; + + if (typeBool || !mapping) return key; + + logWarn(`Filtered ${parent}${key} property in ortb2 data: expected type ${(mapping.isArray) ? 'array' : mapping.type}`); + }).reduce((result, key) => { + let mapping = deepAccess(ORTB_MAP, path + key); + let modified = {}; + + if (mapping) { + if (mapping.optoutApplies && optout) { + logWarn(`Filtered ${parent}${key} data: pubcid optout found`); + return result; + } + + modified = (mapping.type === 'object' && !mapping.isArray) + ? validateFpd(fpd[key], path + key + '.children.', parent + key + '.') + : (mapping.isArray && mapping.childType) + ? filterArrayData(fpd[key], { type: mapping.childType, isArray: mapping.childisArray }, path + key, parent + key) : fpd[key]; + + // Check if modified data has data and return + (!isEmptyData(modified)) ? result[key] = modified + : logWarn(`Filtered ${parent}${key} property in ortb2 data: empty data found`); + } else { + result[key] = fpd[key]; + } + + return result; + }, {})); + + // Return validated data + return validObject; +} + +/** + * Run validation on global and bidder config data for ortb2 + */ +function runValidations(data) { + let conf = validateFpd(data); + + let bidderDuplicate = { ...config.getBidderConfig() }; + + Object.keys(bidderDuplicate).forEach(bidder => { + let modConf = Object.keys(bidderDuplicate[bidder]).reduce((res, key) => { + let valid = (key !== 'ortb2') ? bidderDuplicate[bidder][key] : validateFpd(bidderDuplicate[bidder][key]); + + if (valid) res[key] = valid; + + return res; + }, {}); + + if (Object.keys(modConf).length) config.setBidderConfig({ bidders: [bidder], config: modConf }); + }); + + return conf; +} + +/** + * Sets default values to ortb2 if exists and adds currency and ortb2 setConfig callbacks on init + */ +export function initSubmodule(fpdConf, data) { + // Checks for existsnece of pubcid optout cookie/storage + // if exists, filters user data out + optout = (STORAGE.cookiesAreEnabled() && STORAGE.getCookie('_pubcid_optout')) || + (STORAGE.hasLocalStorage() && STORAGE.getDataFromLocalStorage('_pubcid_optout')); + + return (!fpdConf.skipValidations) ? runValidations(data) : data; +} + +/** @type {firstPartyDataSubmodule} */ +export const validationSubmodule = { + name: 'validation', + queue: 1, + init: initSubmodule +} + +submodule('firstPartyData', validationSubmodule) diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index 92d665887a5..40e3b3322a7 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { getAdUnitSizes } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -32,7 +32,7 @@ export const spec = { } return validBidRequests.map(bidRequest => { - const sizes = utils.getAdUnitSizes(bidRequest); + const sizes = getAdUnitSizes(bidRequest); const payload = { placementId: bidRequest.params.placementId, sizes: sizes, @@ -98,8 +98,14 @@ export const spec = { } else { bidResponse.ad = adCreative; } + if (response.adDomain) { + bidResponse.meta = { + advertiserDomains: response.adDomain + } + } bidResponses.push(bidResponse); } + return bidResponses; }, diff --git a/modules/ventes.md b/modules/ventes.md new file mode 100644 index 00000000000..5f0f571ecd8 --- /dev/null +++ b/modules/ventes.md @@ -0,0 +1,71 @@ +--- +layout: bidder +title: ventes +description: Prebid ventes Bidder Adapter +pbjs: false +biddercode: ventes +gdpr_supported: false +usp_supported: false +media_types: banner +coppa_supported: false +schain_supported: false +dchain_supported: false +prebid_member: false +--- + +### BidParams +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +|-----------------|----------|-----------------------------------------------------------|----------------------------------------------|---------------| +| `placementId` | required | Placement ID from Ventes Avenues | `'VA-062-0013-0183'` | `string` | +| `publisherId` | required | Publisher ID from Ventes Avenues | `'VA-062'` | `string` | +| `user` | optional | Object that specifies information about an external user. | `user: { age: 25, gender: 0, dnt: true}` | `object` | +| `app` | optional | Object containing mobile app parameters. | `app : { id: 'app-id'}` | `object` | + +#### User Object + +{: .table .table-bordered .table-striped } +| Name | Description | Example | Type | +|-------------------|-------------------------------------------------------------------------------------------|-----------------------|-----------------------| +| `age` | The age of the user. | `35` | `integer` | +| `externalUid` | Specifies a string that corresponds to an external user ID for this user. | `'1234567890abcdefg'` | `string` | +| `segments` | Specifies the segments to which the user belongs. | `[1, 2]` | `Array` | +| `gender` | Specifies the gender of the user. Allowed values: Unknown: `0`; Male: `1`; Female: `2` | `1` | `integer` | +| `dnt` | Do not track flag. Indicates if tracking cookies should be disabled for this auction | `true` | `boolean` | +| `language` | Two-letter ANSI code for this user's language. | `EN` | `string` | + + +### Ad Unit Setup for Banner +```javascript +var adUnits = [ +{ + code: 'test-hb-ad-11111-1', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [{ + bidder: 'ventes', + params: { + placementId: 'VA-062-0013-0183', + publisherId: '5cebea3c9eea646c7b623d5e', + IABCategories: "['IAB1', 'IAB5']", + device:{ + ip: '123.145.167.189', + ifa:"AEBE52E7-03EE-455A-B3C4-E57283966239", + }, + app: { + id: "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA", + name: "Yahoo Weather", + bundle: 'com.kiloo.subwaysurf', + storeurl: 'https://play.google.com/store/apps/details?id=com.kiloo.subwaysurf&hl=en', + domain: 'somoaudience.com', + } + } + }] + } +] +``` diff --git a/modules/ventesBidAdapter.js b/modules/ventesBidAdapter.js new file mode 100644 index 00000000000..a9de52a86ba --- /dev/null +++ b/modules/ventesBidAdapter.js @@ -0,0 +1,410 @@ +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'; + +const BID_METHOD = 'POST'; +const BIDDER_URL = 'http://13.234.201.146:8088/va/ad'; + +const DOMAIN_REGEX = new RegExp('//([^/]*)'); + +function groupBy(values, key) { + const groups = values.reduce((acc, value) => { + const groupId = value[key]; + + if (!acc[groupId]) acc[groupId] = []; + acc[groupId].push(value); + + return acc; + }, {}); + + return Object + .keys(groups) + .map(id => ({ + id, + key, + values: groups[id] + })); +} + +function validateMediaTypes(mediaTypes, allowedMediaTypes) { + if (!isPlainObject(mediaTypes)) return false; + if (!allowedMediaTypes.some(mediaType => mediaType in mediaTypes)) return false; + + if (isBanner(mediaTypes)) { + if (!validateBanner(mediaTypes.banner)) return false; + } + return true; +} + +function isBanner(mediaTypes) { + return isPlainObject(mediaTypes) && isPlainObject(mediaTypes.banner); +} + +function validateBanner(banner) { + return isPlainObject(banner) && + isArray(banner.sizes) && + (banner.sizes.length > 0) && + banner.sizes.every(validateMediaSizes); +} + +function validateMediaSizes(mediaSize) { + return isArray(mediaSize) && + (mediaSize.length === 2) && + mediaSize.every(size => (isNumber(size) && size >= 0)); +} + +function hasUserInfo(bid) { + return !!bid.params.user; +} + +function validateParameters(parameters) { + if (!(parameters.placementId)) { + return false; + } + if (!(parameters.publisherId)) { + return false; + } + + return true; +} + +function extractSiteDomainFromURL(url) { + if (!url || !isStr(url)) return null; + + const domain = url.match(DOMAIN_REGEX); + + if (isArray(domain) && domain.length === 2) return domain[1]; + + return null; +} + +function generateSiteFromAdUnitContext(bidRequests, adUnitContext) { + if (!adUnitContext || !adUnitContext.refererInfo) return null; + + const domain = extractSiteDomainFromURL(adUnitContext.refererInfo.referer); + const publisherId = bidRequests[0].params.publisherId; + + if (!domain) return null; + + return { + page: adUnitContext.refererInfo.referer, + domain: domain, + name: domain, + publisher: { + id: publisherId + } + }; +} + +function validateServerRequest(serverRequest) { + return isPlainObject(serverRequest) && + isPlainObject(serverRequest.data) && + isArray(serverRequest.data.imp) +} + +function createServerRequestFromAdUnits(adUnits, bidRequestId, adUnitContext) { + return { + method: BID_METHOD, + url: BIDDER_URL, + data: generateBidRequestsFromAdUnits(adUnits, bidRequestId, adUnitContext), + options: { + contentType: 'application/json', + withCredentials: false, + } + } +} + +function generateBidRequestsFromAdUnits(bidRequests, bidRequestId, adUnitContext) { + const userObjBid = find(bidRequests, hasUserInfo); + let userObj = {}; + if (userObjBid) { + Object.keys(userObjBid.params.user) + .forEach((param) => { + let uparam = convertCamelToUnderscore(param); + if (param === 'segments' && isArray(userObjBid.params.user[param])) { + let segs = []; + userObjBid.params.user[param].forEach(val => { + if (isNumber(val)) { + segs.push({ + 'id': val + }); + } else if (isPlainObject(val)) { + segs.push(val); + } + }); + userObj[uparam] = segs; + } else if (param !== 'segments') { + userObj[uparam] = userObjBid.params.user[param]; + } + }); + } + + const deviceObjBid = find(bidRequests, hasDeviceInfo); + let deviceObj; + if (deviceObjBid && deviceObjBid.params && deviceObjBid.params.device) { + deviceObj = {}; + Object.keys(deviceObjBid.params.device) + .forEach(param => deviceObj[param] = deviceObjBid.params.device[param]); + if (!deviceObjBid.hasOwnProperty('ua')) { + deviceObj.ua = navigator.userAgent; + } + if (!deviceObjBid.hasOwnProperty('language')) { + deviceObj.language = navigator.language; + } + } else { + deviceObj = {}; + deviceObj.ua = navigator.userAgent; + deviceObj.language = navigator.language; + } + + const payload = {} + payload.id = bidRequestId + payload.at = 1 + payload.cur = ['USD'] + payload.imp = bidRequests.reduce(generateImpressionsFromAdUnit, []) + const appDeviceObjBid = find(bidRequests, hasAppInfo); + if (!appDeviceObjBid) { + payload.site = generateSiteFromAdUnitContext(bidRequests, adUnitContext) + } else { + let appIdObj; + if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { + appIdObj = {}; + Object.keys(appDeviceObjBid.params.app) + .forEach(param => appIdObj[param] = appDeviceObjBid.params.app[param]); + } + payload.app = appIdObj; + } + payload.device = deviceObj; + payload.user = userObj + return payload +} + +function generateImpressionsFromAdUnit(acc, adUnit) { + const { + bidId, + mediaTypes, + params + } = adUnit; + const { + placementId + } = params; + const pmp = {}; + + if (placementId) pmp.deals = [{ id: placementId }] + + const imps = Object + .keys(mediaTypes) + .reduce((acc, mediaType) => { + const data = mediaTypes[mediaType]; + const impId = `${bidId}`; + + if (mediaType === 'banner') return acc.concat(generateBannerFromAdUnit(impId, data, params)); + }, []); + + return acc.concat(imps); +} + +function generateBannerFromAdUnit(impId, data, params) { + const { + position, + placementId + } = params; + const pos = position || 0; + const pmp = {}; + const ext = { + placementId + }; + + if (placementId) pmp.deals = [{ id: placementId }] + + return data.sizes.map(([w, h]) => ({ + id: `${impId}`, + banner: { + format: [{ + w, + h + }], + w, + h, + pos + }, + pmp, + ext, + tagid: placementId + })); +} + +function validateServerResponse(serverResponse) { + return isPlainObject(serverResponse) && + isPlainObject(serverResponse.body) && + isStr(serverResponse.body.cur) && + isArray(serverResponse.body.seatbid); +} + +function seatBidsToAds(seatBid, bidResponse, serverRequest) { + return seatBid.bid + .filter(bid => validateBids(bid)) + .map(bid => generateAdFromBid(bid, bidResponse)); +} + +function validateBids(bid) { + if (!isPlainObject(bid)) return false; + if (!isStr(bid.impid)) return false; + if (!isStr(bid.crid)) return false; + if (!isNumber(bid.price)) return false; + if (!isNumber(bid.w)) return false; + if (!isNumber(bid.h)) return false; + if (!bid.adm) return false; + if (bid.adm) { + if (!isStr(bid.adm)) return false; + } + return true; +} + +function getMediaType(adm) { + const videoRegex = new RegExp(/VAST\s+version/); + + if (videoRegex.test(adm)) { + return VIDEO; + } + + const markup = safeJSONparse(adm.replace(/\\/g, '')); + + if (markup && isPlainObject(markup.native)) { + return NATIVE; + } + + return BANNER; +} + +function safeJSONparse(...args) { + try { + return JSON.parse(...args); + } catch (_) { + return undefined; + } +} + +function generateAdFromBid(bid, bidResponse) { + const mediaType = getMediaType(bid.adm); + const base = { + requestId: bid.impid, + cpm: bid.price, + currency: bidResponse.cur, + ttl: 10, + creativeId: bid.crid, + mediaType: mediaType, + netRevenue: true + }; + + if (bid.adomain) { + base.meta = { + advertiserDomains: bid.adomain + }; + } + + const size = getSizeFromBid(bid); + const creative = getCreativeFromBid(bid); + + return { + ...base, + height: size.height, + width: size.width, + ad: creative.markup, + adUrl: creative.markupUrl, + renderer: creative.renderer + }; +} + +function getSizeFromBid(bid) { + if (isNumber(bid.w) && isNumber(bid.h)) { + return { + width: bid.w, + height: bid.h + }; + } + return { + width: null, + height: null + }; +} + +function getCreativeFromBid(bid) { + const shouldUseAdMarkup = !!bid.adm; + const price = bid.price; + return { + markup: shouldUseAdMarkup ? replaceAuctionPrice(bid.adm, price) : null, + markupUrl: !shouldUseAdMarkup ? replaceAuctionPrice(bid.nurl, price) : null + }; +} + +function hasDeviceInfo(bid) { + if (bid.params) { + return !!bid.params.device + } +} + +function hasAppInfo(bid) { + if (bid.params) { + return !!bid.params.app + } +} + +const venavenBidderSpec = { + code: 'ventes', + supportedMediaTypes: [BANNER], + isBidRequestValid(adUnit) { + const allowedBidderCodes = [this.code]; + + return isPlainObject(adUnit) && + allowedBidderCodes.indexOf(adUnit.bidder) !== -1 && + isStr(adUnit.adUnitCode) && + isStr(adUnit.bidderRequestId) && + isStr(adUnit.bidId) && + validateMediaTypes(adUnit.mediaTypes, this.supportedMediaTypes) && + validateParameters(adUnit.params); + }, + buildRequests(bidRequests, bidderRequest) { + if (!bidRequests) return null; + + return groupBy(bidRequests, 'bidderRequestId').map(group => { + const bidRequestId = group.id; + const adUnits = groupBy(group.values, 'bidId').map((group) => { + const length = group.values.length; + return length > 0 && group.values[length - 1] + }); + + return createServerRequestFromAdUnits(adUnits, bidRequestId, bidderRequest) + }); + }, + interpretResponse(serverResponse, serverRequest) { + if (!validateServerRequest(serverRequest)) return []; + if (!validateServerResponse(serverResponse)) return []; + + const bidResponse = serverResponse.body; + + return bidResponse.seatbid + .filter(seatBid => isPlainObject(seatBid) && isArray(seatBid.bid)) + .reduce((acc, seatBid) => acc.concat(seatBidsToAds(seatBid, bidResponse, serverRequest)), []); + } +}; + +registerBidder(venavenBidderSpec); + +export { + venavenBidderSpec as spec +}; diff --git a/modules/ventesBidAdapter.md b/modules/ventesBidAdapter.md new file mode 100644 index 00000000000..c79ef080cd1 --- /dev/null +++ b/modules/ventesBidAdapter.md @@ -0,0 +1,93 @@ +--- +layout: bidder +title: ventes +description: Prebid ventes Bidder Adapter +pbjs: false +biddercode: ventes +gdpr_supported: false +usp_supported: false +media_types: banner +coppa_supported: false +schain_supported: false +dchain_supported: false +prebid_member: false +--- + +### BidParams +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +|-----------------|----------|-----------------------------------------------------------|----------------------------------------------|---------------| +| `placementId` | required | Placement ID from Ventes Avenues | `'VA-062-0013-0183'` | `string` | +| `publisherId` | required | Publisher ID from Ventes Avenues | `'VA-062'` | `string` | +| `user` | optional | Object that specifies information about an external user. | `user: { age: 25, gender: 0, dnt: true}` | `object` | +| `app` | required | Object containing mobile app parameters. | `app : { id: 'app-id'}` | `object` | +| `device` | required | Object containing device info mandatory for mobile devices| `device : { ifa: 'device-id'}` | `object` | + +#### User Object + +{: .table .table-bordered .table-striped } +| Name | Description | Example | Type | +|-------------------|-------------------------------------------------------------------------------------------|-----------------------|-----------------------| +| `age` | The age of the user. | `35` | `integer` | +| `externalUid` | Specifies a string that corresponds to an external user ID for this user. | `'1234567890abcdefg'` | `string` | +| `segments` | Specifies the segments to which the user belongs. | `[1, 2]` | `Array` | +| `gender` | Specifies the gender of the user. Allowed values: Unknown: `0`; Male: `1`; Female: `2` | `1` | `integer` | +| `dnt` | Do not track flag. Indicates if tracking cookies should be disabled for this auction | `true` | `boolean` | +| `language` | Two-letter ANSI code for this user's language. | `EN` | `string` | + + +### Ad Unit Setup for Banner through mobile devices +```javascript +var adUnits = [ +{ + code: 'test-hb-ad-11111-1', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [{ + bidder: 'ventes', + params: { + placementId: 'VA-062-0013-0183', + publisherId: '5cebea3c9eea646c7b623d5e', + IABCategories: "['IAB1', 'IAB5']", + device:{ + ifa:"AEBE52E7-03EE-455A-B3C4-E57283966239", + }, + app: { + id: "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA", + name: "Yahoo Weather", + bundle: 'com.kiloo.subwaysurf', + storeurl: 'https://play.google.com/store/apps/details?id=com.kiloo.subwaysurf&hl=en', + domain: 'somoaudience.com', + } + } + }] + } +] +``` + +### Ad Unit Setup for Banner through Websites +```javascript +var adUnits = [ +{ + code: 'test-hb-ad-11111-1', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [{ + bidder: 'ventes', + params: { + placementId: 'VA-002-0007-0799', + publisherId: '5cebea3c9eea646c7b623d5e', + } + }] + } +] diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js index ca395087d2d..280a6c47894 100644 --- a/modules/verizonMediaIdSystem.js +++ b/modules/verizonMediaIdSystem.js @@ -7,7 +7,7 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; -import * as utils from '../src/utils.js'; +import { logError, formatQS } from '../src/utils.js'; import includes from 'core-js-pure/features/array/includes.js'; const MODULE_NAME = 'verizonMediaId'; @@ -51,7 +51,7 @@ export const verizonMediaIdSubmodule = { const params = config.params || {}; if (!params || typeof params.he !== 'string' || (typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) { - utils.logError('The verizonMediaId submodule requires the \'he\' and \'pixelId\' parameters to be defined.'); + logError('The verizonMediaId submodule requires the \'he\' and \'pixelId\' parameters to be defined.'); return; } @@ -75,18 +75,18 @@ export const verizonMediaIdSubmodule = { try { responseObj = JSON.parse(response); } catch (error) { - utils.logError(error); + logError(error); } } callback(responseObj); }, error: error => { - utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); callback(); } }; const endpoint = VMCID_ENDPOINT.replace(PLACEHOLDER, params.pixelId); - let url = `${params.endpoint || endpoint}?${utils.formatQS(data)}`; + let url = `${params.endpoint || endpoint}?${formatQS(data)}`; verizonMediaIdSubmodule.getAjaxFn()(url, callbacks, null, {method: 'GET', withCredentials: true}); }; return {callback: resp}; diff --git a/modules/viBidAdapter.js b/modules/viBidAdapter.js deleted file mode 100644 index 4a09a2d2fc1..00000000000 --- a/modules/viBidAdapter.js +++ /dev/null @@ -1,393 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import * as mediaTypes from '../src/mediaTypes.js'; - -export function get(path, obj, notFound) { - path = typeof path === 'string' ? path.split('.') : path; - - while (path.length) { - const [key] = path; - if (!(obj instanceof Object) || !(key in obj)) return notFound; - obj = obj[key]; - path = path.slice(1); - } - - return obj; -} - -export function merge(a, b, fn = a => a) { - const res = {}; - - for (const key in a) { - if (key in b) { - res[key] = fn(a[key], b[key]); - } else { - res[key] = a[key]; - } - } - - for (const key in b) { - if (!(key in a)) res[key] = b[key]; - } - - return res; -} - -export function ratioToPercentageCeil(x) { - return Math.ceil(x * 100); -} - -export function getDocumentHeight(curDocument = document) { - return Math.max( - get('body.clientHeight', curDocument, 0), - get('body.scrollHeight', curDocument, 0), - get('body.offsetHeight', curDocument, 0), - get('documentElement.clientHeight', curDocument, 0), - get('documentElement.scrollHeight', curDocument, 0), - get('documentElement.offsetHeight', curDocument, 0) - ); -} - -export function getOffset(element) { - const rect = element.getBoundingClientRect(); - const elementWindow = getElementWindow(element); - if (!elementWindow) throw new Error('cannot get element window'); - const scrollLeft = - elementWindow.pageXOffset || get('documentElement.scrollLeft', document, 0); - const scrollTop = - elementWindow.pageYOffset || get('documentElement.scrollTop', document, 0); - return { - top: rect.top + scrollTop, - right: rect.right + scrollLeft, - bottom: rect.bottom + scrollTop, - left: rect.left + scrollLeft - }; -} - -var IframeType; - -(function(IframeType) { - IframeType['safeframe'] = 'safeframe'; - IframeType['friendly'] = 'friendly'; - IframeType['nonfriendly'] = 'nonfriendly'; -})(IframeType || (IframeType = {})); - -export function getWindowParents(curWindow = window) { - const parents = []; - - while (curWindow && curWindow.parent && curWindow !== curWindow.parent) { - parents.push(curWindow.parent); - curWindow = curWindow.parent; - } - - return parents; -} - -export function getTopmostReachableWindow(curWindow = window) { - const parents = getWindowParents(curWindow); - return parents.length ? parents[parents.length - 1] : curWindow; -} - -export function topDocumentIsReachable(curWindow = window) { - if (!isInsideIframe(curWindow)) return true; - const windowParents = getWindowParents(curWindow); - - try { - const topWindow = windowParents[windowParents.length - 1]; - - return topWindow === curWindow.top && !!curWindow.top.document; - } catch (e) { - return false; - } -} - -export function isInsideIframe(curWindow = window) { - return curWindow !== curWindow.top; -} - -export function isInsideSafeframe(curWindow = window) { - return !topDocumentIsReachable(curWindow) && !!curWindow.$sf; -} - -export function isInsideFriendlyIframe(curWindow = window) { - return isInsideIframe(curWindow) && topDocumentIsReachable(curWindow); -} - -export function getIframeType(curWindow = window) { - if (!isInsideIframe(curWindow)) return; - if (isInsideSafeframe(curWindow)) return IframeType.safeframe; - if (isInsideFriendlyIframe(curWindow)) return IframeType.friendly; - return IframeType.nonfriendly; -} - -function getElementWindow(element) { - return element.ownerDocument - ? element.ownerDocument.defaultView - : element.defaultView; -} - -const NO_CUTS = { - top: 0, - right: 0, - bottom: 0, - left: 0 -}; - -export function getRectCuts(rect, vh, vw, vCuts = NO_CUTS) { - let { top, left } = rect; - const { bottom, right } = rect; - top = top + vCuts.top; - left = left + vCuts.left; - vh = vh + vCuts.bottom; - vw = vw + vCuts.right; - return { - bottom: Math.min(0, vh - bottom), - left: Math.min(0, left), - right: Math.min(0, vw - right), - top: Math.min(0, top) - }; -} - -export function getFrameElements(curWindow = window) { - const frameElements = []; - - while (curWindow && curWindow.frameElement) { - frameElements.unshift(curWindow.frameElement); - curWindow = - curWindow.frameElement.ownerDocument && - curWindow.frameElement.ownerDocument.defaultView; - } - - return frameElements; -} - -export function getElementCuts(element, vCuts) { - const window = getElementWindow(element); - return getRectCuts( - element.getBoundingClientRect(), - window ? window.innerHeight : 0, - window ? window.innerWidth : 0, - vCuts - ); -} - -export function area(width, height, areaCuts = NO_CUTS) { - const { top, right, bottom, left } = areaCuts; - return Math.max(0, (width + left + right) * (height + top + bottom)); -} - -export function getInViewRatio(element) { - const elements = [...getFrameElements(getElementWindow(element)), element]; - const vCuts = elements.reduce( - (vCuts, element) => getElementCuts(element, vCuts), - NO_CUTS - ); - return ( - area(element.offsetWidth || 1, element.offsetHeight || 1, vCuts) / - area(element.offsetWidth || 1, element.offsetHeight || 1) - ); -} - -export function getInViewRatioInsideTopFrame(element) { - const elements = [...getFrameElements().slice(1), element]; - const vCuts = elements.reduce( - (vCuts, element) => getElementCuts(element, vCuts), - NO_CUTS - ); - return ( - area(element.offsetWidth, element.offsetHeight, vCuts) / - area(element.offsetWidth, element.offsetHeight) - ); -} - -export function getMayBecomeVisible(element) { - return !isInsideIframe() || !!getInViewRatioInsideTopFrame(element); -} - -export function getInViewPercentage(element) { - return ratioToPercentageCeil(getInViewRatio(element)); -} - -export function getOffsetTopDocument(element) { - return [...getFrameElements(getElementWindow(element)), element].reduce( - (acc, elem) => merge(acc, getOffset(elem), (a, b) => a + b), - { - top: 0, - right: 0, - bottom: 0, - left: 0 - } - ); -} - -export function getOffsetTopDocumentPercentage(element) { - const elementWindow = getElementWindow(element); - if (!elementWindow) throw new Error('cannot get element window'); - if (!topDocumentIsReachable(elementWindow)) { - throw new Error("top window isn't reachable"); - } - const topWindow = getTopmostReachableWindow(elementWindow); - const documentHeight = getDocumentHeight(topWindow.document); - return ratioToPercentageCeil( - getOffsetTopDocument(element).top / documentHeight - ); -} - -export function getOffsetToView(element) { - const elemWindow = getElementWindow(element); - if (!elemWindow) throw new Error('cannot get element window'); - const topWindow = getTopmostReachableWindow(elemWindow); - const { top, bottom } = getOffsetTopDocument(element); - const topWindowHeight = topWindow.innerHeight; - - if (bottom < topWindow.scrollY) return bottom - topWindow.scrollY; - - if (top > topWindow.scrollY + topWindowHeight) { - return top - topWindow.scrollY - topWindowHeight; - } - - return 0; -} - -export function getOffsetToViewPercentage(element) { - return ratioToPercentageCeil( - getOffsetToView(element) / - getDocumentHeight( - getTopmostReachableWindow(getElementWindow(element)).document - ) - ); -} - -export function getViewabilityDescription(element) { - let iframeType; - try { - if (!element) { - return { - error: 'no element' - }; - } - iframeType = getIframeType(getElementWindow(element)); - if (!iframeType || iframeType === IframeType.friendly) { - const inViewPercentage = getInViewPercentage(element); - return { - inView: inViewPercentage, - hidden: !inViewPercentage && !getMayBecomeVisible(element), - offsetTop: getOffsetTopDocumentPercentage(element), - offsetView: getOffsetToViewPercentage(element), - iframeType - }; - } - return { - iframeType - }; - } catch (error) { - return { - iframeType, - error: error.message - }; - } -} - -export function mergeArrays(hashFn, ...args) { - const seen = {}; - const merged = []; - args.forEach(sizes => { - sizes.forEach(size => { - const key = hashFn(size); - if (!(key in seen)) { - seen[key] = true; - merged.push(size); - } - }); - }); - return merged; -} - -export function documentFocus(doc) { - return typeof doc.hasFocus === 'function' ? +doc.hasFocus() : undefined; -} - -const spec = { - code: 'vi', - supportedMediaTypes: [mediaTypes.VIDEO, mediaTypes.BANNER], - - isBidRequestValid({ adUnitCode, params: { pubId, lang, cat } = {} }) { - return [pubId, lang, cat].every(x => typeof x === 'string'); - }, - - /** - * - * @param bidRequests - * @param bidderRequest - * @return { - * {method: string, - * data: { - imps: { - bidId: string, - adUnitCode: string, - sizes: [[number, number]], - pubId: string, - lang: string, - cat: string, - iframeType: string | undefined, - error: string | null, - inView: number, - offsetTop: number, - offsetView: number, - hidden: boolean, - bidFloor: number - }[], - refererInfo: { - referer: string - reachedTop: boolean, - numIframes: number, - stack: string[] - canonicalUrl: string - } - }, - * options: {withCredentials: boolean, contentType: string}, url: string}} - */ - buildRequests(bidRequests, bidderRequest) { - return { - method: 'POST', - url: 'https://pb.vi-serve.com/prebid/bid', - data: { - refererInfo: bidderRequest.refererInfo, - imps: bidRequests.map( - ({ bidId, adUnitCode, sizes, params, mediaTypes }) => { - const slot = document.getElementById(adUnitCode); - const bannerSizes = get('banner.sizes', mediaTypes); - const playerSize = get('video.playerSize', mediaTypes); - - const sizesToMerge = []; - if (!params.useSizes) { - if (sizes) sizesToMerge.push(sizes); - if (bannerSizes) sizesToMerge.push(bannerSizes); - if (playerSize) sizesToMerge.push(playerSize); - } else if (params.useSizes === 'banner' && bannerSizes) { - sizesToMerge.push(bannerSizes); - } else if (params.useSizes === 'video' && playerSize) { - sizesToMerge.push(playerSize); - } - return { - bidId, - adUnitCode, - sizes: mergeArrays(x => x.join(','), ...sizesToMerge), - ...getViewabilityDescription(slot), - ...params - }; - } - ), - focus: documentFocus(document) - }, - options: { - contentType: 'application/json', - withCredentials: true - } - }; - }, - - interpretResponse({ body }) { - return body; - } -}; -registerBidder(spec); diff --git a/modules/vibrantmediaBidAdapter.js b/modules/vibrantmediaBidAdapter.js new file mode 100644 index 00000000000..b6fe51c43bc --- /dev/null +++ b/modules/vibrantmediaBidAdapter.js @@ -0,0 +1,220 @@ +/* + * Vibrant Media Ltd. + * + * Prebid Adapter for sending bid requests to the prebid server and bid responses back to the client + * + * Note: Only BANNER and VIDEO are currently supported by the prebid server. + */ + +import {logError, logInfo} 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 SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; + +/** + * Returns whether the given bid request contains at least one supported media request, which has valid data. (We can + * ignore invalid/unsupported ones, as they will be filtered out by the prebid server.) + * + * @param {*} bidRequest the bid requests sent by the Prebid API. + * + * @return {boolean} true if the given bid request contains at least one supported media request with valid details, + * otherwise false. + */ +const areValidSupportedMediaTypesPresent = function(bidRequest) { + const mediaTypes = Object.keys(bidRequest.mediaTypes); + + return mediaTypes.some(function(mediaType) { + if (mediaType === BANNER) { + return true; + } else if (mediaType === VIDEO) { + return (bidRequest.mediaTypes[VIDEO].context === OUTSTREAM); + } else if (mediaType === NATIVE) { + return !!bidRequest.mediaTypes[NATIVE].image; + } + + return false; + }); +}; + +/** + * Returns whether the given URL contains just a domain, and not (for example) a subdirectory or query parameters. + * @param {string} url the URL to check. + * @returns {boolean} whether the URL contains just a domain. + */ +const isBaseUrl = function(url) { + const urlMinusScheme = url.substring(url.indexOf('://') + 3); + const endOfDomain = urlMinusScheme.indexOf('/'); + return (endOfDomain === -1) || (endOfDomain === (urlMinusScheme.length - 1)); +}; + +/** + * Returns transformed bid requests that are in a format native to the prebid server. + * + * @param {*[]} bidRequests the bid requests sent by the Prebid API. + * + * @returns {*[]} the transformed bid requests. + */ +const transformBidRequests = function(bidRequests) { + const transformedBidRequests = []; + + bidRequests.forEach(function(bidRequest) { + const params = bidRequest.params || {}; + const transformedBidRequest = { + code: bidRequest.adUnitCode || bidRequest.code, + id: bidRequest.placementId || params.placementId || params.invCode, + requestId: bidRequest.bidId || bidRequest.transactionId, + bidder: bidRequest.bidder, + mediaTypes: bidRequest.mediaTypes, + bids: bidRequest.bids, + sizes: bidRequest.sizes + }; + + transformedBidRequests.push(transformedBidRequest); + }); + + return transformedBidRequests; +}; + +/** @type {BidderSpec} */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + + /** + * Transforms the 'raw' bid params into ones that this adapter can use, prior to creating the bid request. + * + * @param {object} bidParams the params to transform. + * + * @returns {object} the bid params. + */ + transformBidParams: function(bidParams) { + return bidParams; + }, + + /** + * Determines whether or not the given bid request is valid. For all bid requests passed to the buildRequests + * function, each will have been passed to this function and this function will have returned true. + * + * @param {object} bid the bid params to validate. + * + * @return {boolean} true if this is a valid bid, otherwise false. + * @see SUPPORTED_MEDIA_TYPES + */ + isBidRequestValid: function(bid) { + const areBidRequestParamsValid = !!(bid.params.placementId || (bid.params.member && bid.params.invCode)); + return areBidRequestParamsValid && areValidSupportedMediaTypesPresent(bid); + }, + + /** + * Return a prebid server request from the list of bid requests. + * + * @param {BidRequest[]} validBidRequests an array of bids validated via the isBidRequestValid function. + * @param {BidderRequest} bidderRequest an object with data common to all bid requests. + * + * @return ServerRequest Info describing the request to the prebid server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const transformedBidRequests = transformBidRequests(validBidRequests); + + var url = window.parent.location.href; + + if ((window.self === window.top) && (!url || (url.substr(0, 4) !== 'http') || isBaseUrl(url))) { + url = document.URL; + } + + url = encodeURIComponent(url); + + const prebidData = { + url, + gdpr: bidderRequest.gdprConsent, + usp: bidderRequest.uspConsent, + window: { + width: window.innerWidth, + height: window.innerHeight, + }, + biddata: transformedBidRequests, + }; + + return { + method: 'POST', + url: VIBRANT_MEDIA_PREBID_URL, + data: JSON.stringify(prebidData) + }; + }, + + /** + * Translate the Kormorant prebid server response into a list of bids. + * + * @param {ServerResponse} serverResponse a successful response from the prebid server. + * @param {BidRequest} bidRequest the original bid request associated with this response. + * + * @return {Bid[]} an array of bids returned by the prebid server, translated into the expected Prebid.js format. + */ + interpretResponse: function(serverResponse, bidRequest) { + const bids = serverResponse.body; + + bids.forEach(function(bid) { + bid.adResponse = serverResponse; + }); + + return bids; + }, + + /** + * Called if the Prebid API gives up waiting for a prebid server response. + * + * Example timeout data: + * + * [{ + * "bidder": "example", + * "bidId": "51ef8751f9aead", + * "params": { + * ... + * }, + * "adUnitCode": "div-gpt-ad-1460505748561-0", + * "timeout": 3000, + * "auctionId": "18fd8b8b0bd757" + * }] + * + * @param {{}} timeoutData data relating to the timeout. + */ + onTimeout: function(timeoutData) { + logError('Timed out waiting for bids: ' + JSON.stringify(timeoutData)); + }, + + /** + * Called when a bid returned by the prebid server is successful. + * + * Example bid won data: + * + * { + * "bidder": "example", + * "width": 300, + * "height": 250, + * "adId": "330a22bdea4cac", + * "mediaType": "banner", + * "cpm": 0.28 + * "ad": "...", + * "requestId": "418b37f85e772c", + * "adUnitCode": "div-gpt-ad-1460505748561-0", + * "size": "350x250", + * "adserverTargeting": { + * "hb_bidder": "example", + * "hb_adid": "330a22bdea4cac", + * "hb_pb": "0.20", + * "hb_size": "350x250" + * } + * } + * + * @param {*} bidData the data associated with the won bid. See example above for data format. + */ + onBidWon: function(bidData) { + logInfo('Bid won: ' + JSON.stringify(bidData)); + } +}; + +registerBidder(spec); diff --git a/modules/vibrantmediaBidAdapter.md b/modules/vibrantmediaBidAdapter.md new file mode 100644 index 00000000000..ce5db42fdb5 --- /dev/null +++ b/modules/vibrantmediaBidAdapter.md @@ -0,0 +1,92 @@ +## Overview + +**Module Name:** Vibrant Media Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** kormorant@vibrantmedia.com + +## Description + +Module that allows Vibrant Media to provide ad bids for banner, native and video (outstream only). + +## Test Parameters + +```javascript +var adUnits = [ + // Banner ad unit + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'vibrantmedia', + params: { + placementId: 12345 + } + }] + }, + + // Video (outstream) ad unit + { + code: 'test-video-outstream', + sizes: [[300, 250]], + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream', + minduration: 1, // Minimum ad duration, in seconds + maxduration: 60, // Maximum ad duration, in seconds + skip: 0, // 1 - true, 0 - false + skipafter: 5, // Number of seconds before the video can be skipped + playbackmethod: [2], // Auto-play without sound + protocols: [1, 2, 3] // VAST 1.0, 2.0 and 3.0 + } + }, + bids: [ + { + bidder: 'vibrantmedia', + params: { + placementId: 67890, + video: { + skippable: true, + playback_method: 'auto_play_sound_off' + } + } + } + ] + }, + + // Native ad unit + { + code: 'test-native', + mediaTypes: { + native: { + image: { + required: true, + sizes: [300, 250] + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + } + }, + bids: [{ + bidder: 'vibrantmedia', + params: { + placementId: 13579, + allowSmallerSizes: true + } + }] + } +]; +``` diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 7fc6e3a5395..b6ab42499b9 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { _each, deepAccess, parseSizesInput } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -48,7 +48,7 @@ function isBidRequestValid(bid) { } function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { - const { params, bidId, userId, adUnitCode } = bid; + const { params, bidId, userId, adUnitCode, schain } = bid; const { bidFloor, ext } = params; const hashUrl = hashCode(topWindowUrl); const dealId = getNextDealId(hashUrl); @@ -71,7 +71,8 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { uniqueDealId: uniqueDealId, bidderVersion: BIDDER_VERSION, prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}` + res: `${screen.width}x${screen.height}`, + schain: schain }; appendUserIdsToRequestPayload(data, userId); @@ -94,7 +95,7 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { data: data }; - utils._each(ext, (value, key) => { + _each(ext, (value, key) => { dto.data['ext.' + key] = value; }); @@ -103,13 +104,13 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { function appendUserIdsToRequestPayload(payloadRef, userIds) { let key; - utils._each(userIds, (userId, idSystemProviderName) => { + _each(userIds, (userId, idSystemProviderName) => { if (SUPPORTED_ID_SYSTEMS[idSystemProviderName]) { key = `uid.${idSystemProviderName}`; switch (idSystemProviderName) { case 'digitrustid': - payloadRef[key] = utils.deepAccess(userId, 'data.id'); + payloadRef[key] = deepAccess(userId, 'data.id'); break; case 'lipb': payloadRef[key] = userId.lipbid; @@ -131,7 +132,7 @@ function buildRequests(validBidRequests, bidderRequest) { const topWindowUrl = bidderRequest.refererInfo.referer; const requests = []; validBidRequests.forEach(validBidRequest => { - const sizes = utils.parseSizesInput(validBidRequest.sizes); + const sizes = parseSizesInput(validBidRequest.sizes); const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest); requests.push(request); }); @@ -149,7 +150,7 @@ function interpretResponse(serverResponse, request) { try { results.forEach(result => { - const { creativeId, ad, price, exp, width, height, currency } = result; + const { creativeId, ad, price, exp, width, height, currency, advertiserDomains } = result; if (!ad || !price) { return; } @@ -162,7 +163,10 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - ad: ad + ad: ad, + meta: { + advertiserDomains: advertiserDomains || [] + } }) }); return output; @@ -266,8 +270,8 @@ export function tryParseJSON(value) { export const spec = { code: BIDDER_CODE, - gvlid: GVLID, version: BIDDER_VERSION, + gvlid: GVLID, supportedMediaTypes: [BANNER], isBidRequestValid, buildRequests, diff --git a/modules/vidazooBidAdapter.md b/modules/vidazooBidAdapter.md index df7700cb05a..c8fd5956d2a 100644 --- a/modules/vidazooBidAdapter.md +++ b/modules/vidazooBidAdapter.md @@ -20,7 +20,7 @@ var adUnits = [ { bidder: 'vidazoo', params: { - cId: '5a1c419d95fce900044c334e', + cId: '562524b21b1c1f08117fc7f9', pId: '59ac17c192832d0011283fe3', bidFloor: 0.0001, ext: { diff --git a/modules/videoNowBidAdapter.js b/modules/videoNowBidAdapter.js deleted file mode 100644 index b391af08d49..00000000000 --- a/modules/videoNowBidAdapter.js +++ /dev/null @@ -1,187 +0,0 @@ -import * as utils from '../src/utils.js' -import { registerBidder } from '../src/adapters/bidderFactory.js' -import { BANNER } from '../src/mediaTypes.js' -import { loadExternalScript } from '../src/adloader.js' - -const RTB_URL = 'https://bidder.videonow.ru/prebid' - -const BIDDER_CODE = 'videonow' -const TTL_SECONDS = 60 * 5 - -function isBidRequestValid(bid) { - return !!(bid && bid.params && bid.params.pId) -} - -function buildRequest(bid, bidderRequest) { - const { refererInfo } = bidderRequest - const { ext, bidId, params, code, sizes } = bid - const { pId, bidFloor, cur, placementId, url: rtbUrl } = params || {} - - let url = rtbUrl || RTB_URL - url = `${url}${~url.indexOf('?') ? '&' : '?'}profile_id=${pId}` - - const dto = { - method: 'POST', - url, - data: { - id: bidId, - cpm: bidFloor, - code, - sizes, - cur: cur || 'RUB', - placementId, - ref: refererInfo && refererInfo.referer, - }, - } - - ext && Object.keys(ext).forEach(key => { - dto.data[`ext_${key}`] = ext[key] - }) - - return dto -} - -function buildRequests(validBidRequests, bidderRequest) { - utils.logInfo(`${BIDDER_CODE}. buildRequests`) - const requests = [] - validBidRequests.forEach(validBidRequest => { - const request = buildRequest(validBidRequest, bidderRequest) - request && requests.push(request) - }) - return requests -} - -function interpretResponse(serverResponse, bidRequest) { - if (!serverResponse || !serverResponse.body) { - return [] - } - const { id: bidId } = (bidRequest && bidRequest.data) || {} - if (!bidId) return [] - - const { seatbid, cur, ext } = serverResponse.body - if (!seatbid || !seatbid.length) return [] - - const { placementId } = ext || {} - if (!placementId) return [] - - const bids = [] - seatbid.forEach(sb => { - const { bid } = sb - bid && bid.length && bid.forEach(b => { - const res = createResponseBid(b, bidId, cur, placementId) - res && bids.push(res) - }) - }) - - return bids -} - -function createResponseBid(bidInfo, bidId, cur, placementId) { - const { id, nurl, code, price, crid, ext, ttl, netRevenue, w, h, adm } = bidInfo - - if (!id || !price || !adm) { - return null - } - - const { init: initPath, module, format } = ext || {} - if (!initPath) { - utils.logError(`vnInitModulePath is not defined`) - return null - } - - const { log, min } = module || {} - - if (!min && !log) { - utils.logError('module\'s paths are not defined') - return null - } - - return { - requestId: bidId, - cpm: price, - width: w, - height: h, - creativeId: crid, - currency: cur || 'RUB', - netRevenue: netRevenue !== undefined ? netRevenue : true, - ttl: ttl || TTL_SECONDS, - ad: code, - nurl, - renderer: { - url: min || log, - render: function() { - const d = window.document - const el = placementId && d.getElementById(placementId) - if (el) { - const pId = 1 - // prepare data for vn_init script - const profileData = { - module, - dataXml: adm, - } - - format && (profileData.format = format) - - // add init data for vn_init on the page - const videonow = window.videonow = window.videonow || {} - const init = videonow.init = window.videonow.init || {} - init[pId] = profileData - - // add vn_init js on the page - loadExternalScript(`${initPath}${~initPath.indexOf('?') ? '&' : '?'}profileId=${pId}`, 'outstream') - } else { - utils.logError(`bidAdapter ${BIDDER_CODE}: ${placementId} not found`) - } - } - } - } -} - -function getUserSyncs(syncOptions, serverResponses) { - const syncs = [] - - if (!serverResponses || !serverResponses.length) return syncs - - serverResponses.forEach(response => { - const { ext } = (response && response.body) || {} - const { pixels, iframes } = ext || {} - - if (syncOptions.iframeEnabled && iframes && iframes.length) { - iframes.forEach(i => syncs.push({ - type: 'iframe', - url: i, - }), - ) - } - - if (syncOptions.pixelEnabled && pixels && pixels.length) { - pixels.forEach(p => syncs.push({ - type: 'image', - url: p, - }), - ) - } - }) - - utils.logInfo(`${BIDDER_CODE} getUserSyncs() syncs=${syncs.length}`) - return syncs -} - -function onBidWon(bid) { - const { nurl } = bid || {} - if (nurl) { - utils.triggerPixel(utils.replaceAuctionPrice(nurl, bid.cpm)); - } -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs, - onBidWon -} - -registerBidder(spec) diff --git a/modules/videobyteBidAdapter.js b/modules/videobyteBidAdapter.js new file mode 100644 index 00000000000..6e99b5bc42a --- /dev/null +++ b/modules/videobyteBidAdapter.js @@ -0,0 +1,318 @@ +import { logMessage, logError, deepAccess, isFn, isPlainObject, isStr, isNumber, isArray, deepSetValue } from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {VIDEO} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'videobyte'; +const DEFAULT_BID_TTL = 300; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; +const VIDEO_ORTB_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'placement', + 'protocols', + 'startdelay', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity' +]; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO], + VERSION: '1.0.0', + ENDPOINT: 'https://x.videobyte.com/ortbhb', + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bidRequest The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bidRequest) { + return validateVideo(bidRequest); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param bidRequests - an array of bid requests + * @param bidderRequest + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests, bidderRequest) { + if (!bidRequests) { + return; + } + return bidRequests.map(bidRequest => { + const {params} = bidRequest; + let pubId = params.pubId; + const placementId = params.placementId; + const nId = params.nid; + if (bidRequest.params.video && bidRequest.params.video.e2etest) { + logMessage('E2E test mode enabled'); + pubId = 'e2etest' + } + let baseEndpoint = spec.ENDPOINT + '?pid=' + pubId; + if (placementId) { + baseEndpoint += '&placementId=' + placementId + } + if (nId) { + baseEndpoint += '&nid=' + nId + } + return { + method: 'POST', + url: baseEndpoint, + data: JSON.stringify(buildRequestData(bidRequest, bidderRequest)), + } + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse) { + const bidResponses = []; + const response = (serverResponse || {}).body; + // one seat with (optional) bids for each impression + if (response && response.seatbid && response.seatbid.length === 1 && response.seatbid[0].bid && response.seatbid[0].bid.length === 1) { + const bid = response.seatbid[0].bid[0] + if (bid.adm && bid.price) { + let bidResponse = { + requestId: response.id, + bidderCode: spec.code, + cpm: bid.price, + width: bid.w, + height: bid.h, + ttl: DEFAULT_BID_TTL, + creativeId: bid.crid, + netRevenue: DEFAULT_NET_REVENUE, + currency: DEFAULT_CURRENCY, + mediaType: 'video', + vastXml: bid.adm, + meta: { + advertiserDomains: bid.adomain + } + }; + 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. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses) { + let syncs = []; + + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return syncs; + } + + serverResponses.forEach(resp => { + const userSync = deepAccess(resp, 'body.ext.usersync'); + if (userSync) { + let syncDetails = []; + Object.keys(userSync).forEach(key => { + const value = userSync[key]; + if (value.syncs && value.syncs.length) { + syncDetails = syncDetails.concat(value.syncs); + } + }); + syncDetails.forEach(syncDetails => { + syncs.push({ + type: syncDetails.type === 'iframe' ? 'iframe' : 'image', + url: syncDetails.url + }); + }); + + // if iframe is enabled return only iframe (videobyte) + // if iframe is disabled, we can proceed to pixels if any + if (syncOptions.iframeEnabled) { + syncs = syncs.filter(s => s.type === 'iframe') + } else if (syncOptions.pixelEnabled) { + syncs = syncs.filter(s => s.type === 'image') + } + } + }); + return syncs; + } + +} + +// BUILD REQUESTS: VIDEO +function buildRequestData(bidRequest, bidderRequest) { + const {params} = bidRequest; + + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + + const videoParams = { + ...videoAdUnit, + ...videoBidderParams // Bidder Specific overrides + }; + + if (bidRequest.params.video && bidRequest.params.video.e2etest) { + videoParams.playerSize = [[640, 480]] + videoParams.conext = 'instream' + } + + const video = { + w: parseInt(videoParams.playerSize[0][0], 10), + h: parseInt(videoParams.playerSize[0][1], 10), + } + + // Obtain all ORTB params related video from Ad Unit + VIDEO_ORTB_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + video[param] = videoParams[param]; + } + }); + + // Placement Inference Rules: + // - If no placement is defined then default to 1 (In Stream) + video.placement = video.placement || 2; + + // - If product is instream (for instream context) then override placement to 1 + if (params.context === 'instream') { + video.startdelay = video.startdelay || 0; + video.placement = 1; + } + + // bid floor + const bidFloorRequest = { + currency: bidRequest.params.cur || 'USD', + mediaType: 'video', + size: '*' + }; + let floorData = bidRequest.params + if (isFn(bidRequest.getFloor)) { + floorData = bidRequest.getFloor(bidFloorRequest); + } else { + if (params.bidfloor) { + floorData = {floor: params.bidfloor, currency: params.currency || 'USD'}; + } + } + + const openrtbRequest = { + id: bidRequest.bidId, + imp: [ + { + id: '1', + video: video, + secure: isSecure() ? 1 : 0, + bidfloor: floorData.floor, + bidfloorcur: floorData.currency + } + ], + site: { + domain: window.location.hostname, + page: window.location.href, + ref: bidRequest.refererInfo ? bidRequest.refererInfo.referer || null : null + }, + ext: { + hb: 1, + prebidver: '$prebid.version$', + adapterver: spec.VERSION, + }, + }; + + // content + if (videoParams.content && isPlainObject(videoParams.content)) { + openrtbRequest.site.content = {}; + const contentStringKeys = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language', 'url']; + const contentNumberkeys = ['episode', 'prodq', 'context', 'livestream', 'len']; + const contentArrayKeys = ['cat']; + const contentObjectKeys = ['ext']; + for (const contentKey in videoBidderParams.content) { + if ( + (contentStringKeys.indexOf(contentKey) > -1 && isStr(videoParams.content[contentKey])) || + (contentNumberkeys.indexOf(contentKey) > -1 && isNumber(videoParams.content[contentKey])) || + (contentObjectKeys.indexOf(contentKey) > -1 && isPlainObject(videoParams.content[contentKey])) || + (contentArrayKeys.indexOf(contentKey) > -1 && isArray(videoParams.content[contentKey]) && + videoParams.content[contentKey].every(catStr => isStr(catStr)))) { + openrtbRequest.site.content[contentKey] = videoParams.content[contentKey]; + } else { + logMessage('videobyte bid adapter validation error: ', contentKey, ' is either not supported is OpenRTB V2.5 or value is undefined'); + } + } + } + + // adding schain object + if (bidRequest.schain) { + deepSetValue(openrtbRequest, 'source.ext.schain', bidRequest.schain); + openrtbRequest.source.ext.schain.nodes[0].rid = openrtbRequest.id; + } + + // Attaching GDPR Consent Params + if (bidderRequest.gdprConsent) { + deepSetValue(openrtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(openrtbRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest.uspConsent) { + deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + return openrtbRequest; +} + +function validateVideo(bidRequest) { + if (!bidRequest.params) { + return false; + } + + if (!bidRequest.params.pubId) { + logError('failed validation: pubId not declared'); + return false; + } + + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + + if (videoBidderParams && videoBidderParams.e2etest) { + return true; + } + + const videoParams = { + ...videoAdUnit, + ...videoBidderParams // Bidder Specific overrides + }; + + if (!videoParams.context) { + logError('failed validation: context id not declared'); + return false; + } + if (videoParams.context !== 'instream') { + logError('failed validation: only context instream is supported '); + return false; + } + + if (typeof videoParams.playerSize === 'undefined' || !Array.isArray(videoParams.playerSize) || !Array.isArray(videoParams.playerSize[0])) { + logError('failed validation: player size not declared or is not in format [[w,h]]'); + return false; + } + + return true; +} + +function isSecure() { + return document.location.protocol === 'https:'; +} + +registerBidder(spec); diff --git a/modules/videobyteBidAdapter.md b/modules/videobyteBidAdapter.md new file mode 100644 index 00000000000..d0037b0972a --- /dev/null +++ b/modules/videobyteBidAdapter.md @@ -0,0 +1,127 @@ +# Overview + +``` +Module Name: VideoByte Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@videobyte.com +``` + +# Description + +Module that connects to VideoByte's demand sources + +*Note:* The Video SSP ad server will respond with an VAST XML to load into your defined player. + +## Instream Video adUnit using mediaTypes.video +*Note:* By default, the adapter will read the mandatory parameters from mediaTypes.video. +*Note:* The Video SSP ad server will respond with an VAST XML to load into your defined player. +``` + var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2], + minduration: 10, + maxduration: 30, + placement: 1, + playbackmethod: [1,5], + protocols: [2,5], + api: [2], + } + }, + bids: [ + { + bidder: 'videobyte', + params: { + bidfloor: 0.5, + pubId: 'e2etest' + } + } + ] + } + ] +``` + +## Instream Video adUnit with placement, nid and content params +``` + var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2], + minduration: 10, + maxduration: 30, + placement: 1, + playbackmethod: [1,5], + protocols: [2,5], + api: [2], + } + }, + bids: [ + { + bidder: 'videobyte', + params: { + bidfloor: 0.5, + pubId: 'e2etest', + placementId: '1234567', + nid: '1234', + video: { + content:{ + id: "uuid", + url: "https://videobyte.com/awesome-video.mp4", + title: "Awesome video", + genre: "Comedy", + language: "en", + season: "1", + series: "1", + + } + } + } + } + ] + } + ] +``` + +# End To End testing mode +By passing bid.params.video.e2etest = true you will be able to receive a test creative + +``` +var adUnits = [ + { + code: 'video-1', + mediaTypes: { + video: { + context: "instream", + playerSize: [[640, 480]], + mimes: ['video/mp4'], + } + }, + bids: [ + { + bidder: 'videobyte', + params: { + video: { + e2etest: true + } + } + } + ] + } +] +``` diff --git a/modules/videofyBidAdapter.js b/modules/videofyBidAdapter.js deleted file mode 100644 index 11bc21303fd..00000000000 --- a/modules/videofyBidAdapter.js +++ /dev/null @@ -1,300 +0,0 @@ -import { VIDEO } from '../src/mediaTypes.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; -import * as utils from '../src/utils.js'; - -const BIDDER_CODE = 'videofy'; -const TTL = 600; - -function avRenderer(bid) { - bid.renderer.push(function() { - let eventCallback = bid && bid.renderer && bid.renderer.handleVideoEvent ? bid.renderer.handleVideoEvent : null; - window.aniviewRenderer.renderAd({ - id: bid.adUnitCode + '_' + bid.adId, - debug: window.location.href.indexOf('pbjsDebug') >= 0, - placement: bid.adUnitCode, - width: bid.width, - height: bid.height, - vastUrl: bid.vastUrl, - vastXml: bid.vastXml, - config: bid.params[0].rendererConfig, - eventsCallback: eventCallback, - bid: bid - }); - }); -} - -function newRenderer(bidRequest) { - const renderer = Renderer.install({ - url: 'https://player.srv-mars.com/script/6.1/prebidRenderer.js', - config: {}, - loaded: false, - }); - - try { - renderer.setRender(avRenderer); - } catch (err) { - } - - return renderer; -} - -function isBidRequestValid(bid) { - if (!bid.params || !bid.params.AV_PUBLISHERID || !bid.params.AV_CHANNELID) { return false; } - - return true; -} -let irc = 0; -function buildRequests(validBidRequests, bidderRequest) { - let bidRequests = []; - - for (let i = 0; i < validBidRequests.length; i++) { - let bidRequest = validBidRequests[i]; - var sizes = [[640, 480]]; - - if (bidRequest.mediaTypes && bidRequest.mediaTypes.video && bidRequest.mediaTypes.video.playerSize) { - sizes = bidRequest.mediaTypes.video.playerSize; - } else { - if (bidRequest.sizes) { - sizes = bidRequest.sizes; - } - } - if (sizes.length === 2 && typeof sizes[0] === 'number') { - sizes = [[sizes[0], sizes[1]]]; - } - - for (let j = 0; j < sizes.length; j++) { - let size = sizes[j]; - let playerWidth; - let playerHeight; - - if (size && size.length == 2) { - playerWidth = size[0]; - playerHeight = size[1]; - } else { - playerWidth = 640; - playerHeight = 480; - } - - let s2sParams = {}; - - for (var attrname in bidRequest.params) { - if (bidRequest.params.hasOwnProperty(attrname) && attrname.indexOf('AV_') == 0) { - s2sParams[attrname] = bidRequest.params[attrname]; - } - }; - - if (s2sParams.AV_APPPKGNAME && !s2sParams.AV_URL) { s2sParams.AV_URL = s2sParams.AV_APPPKGNAME; } - if (!s2sParams.AV_IDFA && !s2sParams.AV_URL) { - if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - s2sParams.AV_URL = bidderRequest.refererInfo.referer; - } else { - s2sParams.AV_URL = window.location.href; - } - } - if (s2sParams.AV_IDFA && !s2sParams.AV_AID) { s2sParams.AV_AID = s2sParams.AV_IDFA; } - if (s2sParams.AV_AID && !s2sParams.AV_IDFA) { s2sParams.AV_IDFA = s2sParams.AV_AID; } - - s2sParams.cb = Math.floor(Math.random() * 999999999); - s2sParams.AV_WIDTH = playerWidth; - s2sParams.AV_HEIGHT = playerHeight; - s2sParams.bidWidth = playerWidth; - s2sParams.bidHeight = playerHeight; - s2sParams.bidId = bidRequest.bidId; - s2sParams.pbjs = 1; - s2sParams.tgt = 10; - s2sParams.s2s = '1'; - s2sParams.irc = irc; - irc++; - s2sParams.wpm = 1; - - if (bidderRequest && bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies) { - s2sParams.AV_GDPR = 1; - s2sParams.AV_CONSENT = bidderRequest.gdprConsent.consentString; - } - } - if (bidderRequest && bidderRequest.uspConsent) { - s2sParams.AV_CCPA = bidderRequest.uspConsent; - } - - let serverDomain = (bidRequest.params && bidRequest.params.serverDomain) ? bidRequest.params.serverDomain : 'servx.srv-mars.com'; - let servingUrl = 'https://' + serverDomain + '/api/adserver/vast3/'; - - bidRequests.push({ - method: 'GET', - url: servingUrl, - data: s2sParams, - bidRequest - }); - } - } - - return bidRequests; -} -function getCpmData(xml) { - let ret = {cpm: 0, currency: 'USD'}; - if (xml) { - let ext = xml.getElementsByTagName('Extensions'); - if (ext && ext.length > 0) { - ext = ext[0].getElementsByTagName('Extension'); - if (ext && ext.length > 0) { - for (var i = 0; i < ext.length; i++) { - if (ext[i].getAttribute('type') == 'ANIVIEW') { - let price = ext[i].getElementsByTagName('Cpm'); - if (price && price.length == 1) { - ret.cpm = price[0].textContent; - } - break; - } - } - } - } - } - return ret; -} -function interpretResponse(serverResponse, bidRequest) { - let bidResponses = []; - if (serverResponse && serverResponse.body) { - if (serverResponse.error) { - return bidResponses; - } else { - try { - let bidResponse = {}; - if (bidRequest && bidRequest.data && bidRequest.data.bidId && bidRequest.data.bidId !== '') { - let xmlStr = serverResponse.body; - let xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); - if (xml && xml.getElementsByTagName('parsererror').length == 0) { - let cpmData = getCpmData(xml); - if (cpmData && cpmData.cpm > 0) { - bidResponse.requestId = bidRequest.data.bidId; - bidResponse.bidderCode = BIDDER_CODE; - bidResponse.ad = ''; - bidResponse.cpm = cpmData.cpm; - bidResponse.width = bidRequest.data.AV_WIDTH; - bidResponse.height = bidRequest.data.AV_HEIGHT; - bidResponse.ttl = TTL; - bidResponse.creativeId = xml.getElementsByTagName('Ad') && xml.getElementsByTagName('Ad')[0] && xml.getElementsByTagName('Ad')[0].getAttribute('id') ? xml.getElementsByTagName('Ad')[0].getAttribute('id') : 'creativeId'; - bidResponse.currency = cpmData.currency; - bidResponse.netRevenue = true; - var blob = new Blob([xmlStr], { - type: 'application/xml' - }); - bidResponse.vastUrl = window.URL.createObjectURL(blob); - bidResponse.vastXml = xmlStr; - bidResponse.mediaType = VIDEO; - if (bidRequest.bidRequest && bidRequest.bidRequest.mediaTypes && bidRequest.bidRequest.mediaTypes.video && bidRequest.bidRequest.mediaTypes.video.context === 'outstream') { bidResponse.renderer = newRenderer(bidRequest); } - - bidResponses.push(bidResponse); - } - } else {} - } else {} - } catch (e) {} - } - } else {} - - return bidResponses; -} - -function getSyncData(xml, options) { - let ret = []; - if (xml) { - let ext = xml.getElementsByTagName('Extensions'); - if (ext && ext.length > 0) { - ext = ext[0].getElementsByTagName('Extension'); - if (ext && ext.length > 0) { - for (var i = 0; i < ext.length; i++) { - if (ext[i].getAttribute('type') == 'ANIVIEW') { - let syncs = ext[i].getElementsByTagName('AdServingSync'); - if (syncs && syncs.length == 1) { - try { - let data = JSON.parse(syncs[0].textContent); - if (data && data.trackers && data.trackers.length) { - data = data.trackers; - for (var j = 0; j < data.length; j++) { - if (typeof data[j] === 'object' && typeof data[j].url === 'string' && data[j].e === 'inventory') { - if (data[j].t == 1 && options.pixelEnabled) { - ret.push({url: data[j].url, type: 'image'}); - } else { - if (data[j].t == 3 && options.iframeEnabled) { - ret.push({url: data[j].url, type: 'iframe'}); - } - } - } - } - } - } catch (e) {} - } - break; - } - } - } - } - } - return ret; -} - -function getUserSyncs(syncOptions, serverResponses) { - if (serverResponses && serverResponses[0] && serverResponses[0].body) { - if (serverResponses.error) { - return []; - } else { - try { - let xmlStr = serverResponses[0].body; - let xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); - if (xml && xml.getElementsByTagName('parsererror').length == 0) { - let syncData = getSyncData(xml, syncOptions); - return syncData; - } - } catch (e) {} - } - } -} - -function onBidWon(bid) { - sendbeacon(bid, 17); -} - -function onTimeout(bid) { - sendbeacon(bid, 19); -} - -function onSetTargeting(bid) { - sendbeacon(bid, 20); -} - -function sendbeacon(bid, type) { - const bidCopy = { - bidder: bid.bidder, - cpm: bid.cpm, - originalCpm: bid.originalCpm, - currency: bid.currency, - originalCurrency: bid.originalCurrency, - timeToRespond: bid.timeToRespond, - statusMessage: bid.statusMessage, - width: bid.width, - height: bid.height, - size: bid.size, - params: bid.params, - status: bid.status, - adserverTargeting: bid.adserverTargeting, - ttl: bid.ttl - }; - const bidString = JSON.stringify(bidCopy); - const encodedBuf = window.btoa(bidString); - utils.triggerPixel('https://beacon.videofy.io/notification/rtb/beacon/?bt=' + type + '&bid=hcwqso&hb_j=' + encodedBuf, null); -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [VIDEO], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs, - onBidWon, - onTimeout, - onSetTargeting -}; - -registerBidder(spec); diff --git a/modules/videoreachBidAdapter.js b/modules/videoreachBidAdapter.js index 710920c54fc..61ecd55a2ef 100644 --- a/modules/videoreachBidAdapter.js +++ b/modules/videoreachBidAdapter.js @@ -1,10 +1,12 @@ +import { getValue, getBidIdParameter } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -const utils = require('../src/utils.js'); const BIDDER_CODE = 'videoreach'; const ENDPOINT_URL = 'https://a.videoreach.com/hb/'; +const GVLID = 547; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: ['banner'], isBidRequestValid: function(bid) { @@ -15,12 +17,12 @@ export const spec = { let data = { data: validBidRequests.map(function(bid) { return { - TagId: utils.getValue(bid.params, 'TagId'), - adUnitCode: utils.getBidIdParameter('adUnitCode', bid), - bidId: utils.getBidIdParameter('bidId', bid), - bidderRequestId: utils.getBidIdParameter('bidderRequestId', bid), - auctionId: utils.getBidIdParameter('auctionId', bid), - transactionId: utils.getBidIdParameter('transactionId', bid) + TagId: getValue(bid.params, 'TagId'), + adUnitCode: getBidIdParameter('adUnitCode', bid), + bidId: getBidIdParameter('bidId', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + auctionId: getBidIdParameter('auctionId', bid), + transactionId: getBidIdParameter('transactionId', bid) } }) }; @@ -58,7 +60,10 @@ export const spec = { ttl: bid.ttl, ad: bid.ad, requestId: bid.bidId, - creativeId: bid.creativeId + creativeId: bid.creativeId, + meta: { + advertiserDomains: bid && bid.adomain ? bid.adomain : [] + } }; bidResponses.push(bidResponse); }); diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js new file mode 100644 index 00000000000..d268f7a9d64 --- /dev/null +++ b/modules/vidoomyBidAdapter.js @@ -0,0 +1,248 @@ +import { logError, deepAccess, 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 { Renderer } from '../src/Renderer.js'; +import { INSTREAM, OUTSTREAM } from '../src/video.js'; + +const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`; +const BIDDER_CODE = 'vidoomy'; +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://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' +]; + +const isBidRequestValid = bid => { + if (!bid.params) { + logError(BIDDER_CODE + ': bid.params should be non-empty'); + return false; + } + + if (!+bid.params.pid) { + logError(BIDDER_CODE + ': bid.params.pid should be non-empty Number'); + return false; + } + + if (!+bid.params.id) { + logError(BIDDER_CODE + ': bid.params.id should be non-empty Number'); + return false; + } + + if (bid.params && bid.params.mediaTypes && bid.params.mediaTypes.video && bid.params.mediaTypes.video.context === INSTREAM && !bid.params.mediaTypes.video.playerSize) { + logError(BIDDER_CODE + ': bid.params.mediaType.video should have a playerSize property to tell player size when is INSTREAM'); + return false; + } + + if (bid.params.bidfloor && (isNaN(bid.params.bidfloor) || bid.params.bidfloor < 0)) { + logError(BIDDER_CODE + ': bid.params.bidfloor should be a number equal or greater than zero'); + return false; + } + + return true; +}; + +const isBidResponseValid = bid => { + if (!bid || !bid.requestId || !bid.cpm || !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + default: + return false; + } +} + +const buildRequests = (validBidRequests, bidderRequest) => { + const serverRequests = validBidRequests.map(bid => { + let adType = BANNER; + let sizes; + if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { + sizes = bid.mediaTypes[BANNER].sizes; + adType = BANNER; + } else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { + sizes = bid.mediaTypes[VIDEO].playerSize; + adType = VIDEO; + } + const [w, h] = (parseSizesInput(sizes)[0] || '0x0').split('x'); + + const aElement = document.createElement('a'); + aElement.href = (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) || top.location.href; + const hostname = aElement.hostname + + const videoContext = deepAccess(bid, 'mediaTypes.video.context'); + const bidfloor = deepAccess(bid, `params.bidfloor`, 0); + + const queryParams = { + id: bid.params.id, + adtype: adType, + auc: bid.adUnitCode, + w, + h, + pos: parseInt(bid.params.position) || 1, + ua: navigator.userAgent, + l: navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '', + dt: /Mobi/.test(navigator.userAgent) ? 2 : 1, + pid: bid.params.pid, + requestId: bid.bidId, + schain: bid.schain || '', + bidfloor, + d: getDomainWithoutSubdomain(hostname), // 'vidoomy.com', + sp: encodeURIComponent(aElement.href), + usp: bidderRequest.uspConsent || '', + coppa: !!config.getConfig('coppa'), + videoContext: videoContext || '' + }; + + if (bidderRequest.gdprConsent) { + queryParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + queryParams.gdprcs = bidderRequest.gdprConsent.consentString; + } + + return { + method: 'GET', + url: ENDPOINT, + data: queryParams + } + }); + return serverRequests; +}; + +const render = (bid) => { + bid.ad = bid.vastUrl; + var obj = { + vastTimeout: 5000, + maxAllowedVastTagRedirects: 3, + allowVpaid: true, + autoPlay: true, + preload: true, + mute: true, + } + window.outstreamPlayer(bid, bid.adUnitCode, obj); +} + +const interpretResponse = (serverResponse, bidRequest) => { + try { + let responseBody = serverResponse.body; + if (!responseBody) return; + if (responseBody.mediaType === 'video') { + responseBody.ad = responseBody.vastUrl || responseBody.vastXml; + const videoContext = bidRequest.data.videoContext; + + if (videoContext === OUTSTREAM) { + try { + const renderer = Renderer.install({ + id: bidRequest.bidId, + adunitcode: bidRequest.tagId, + loaded: false, + config: responseBody.mediaType, + url: responseBody.meta.rendererUrl + }); + renderer.setRender(render); + + responseBody.renderer = renderer; + } catch (e) { + responseBody.ad = responseBody.vastUrl || responseBody.vastXml; + logError(BIDDER_CODE + ': error while installing renderer to show outstream ad'); + } + } + } + const bid = { + ad: responseBody.ad, + renderer: responseBody.renderer, + mediaType: responseBody.mediaType, + requestId: responseBody.requestId, + cpm: responseBody.cpm, + currency: responseBody.currency, + width: responseBody.width, + height: responseBody.height, + creativeId: responseBody.creativeId, + netRevenue: responseBody.netRevenue, + ttl: responseBody.ttl, + meta: { + mediaType: responseBody.meta.mediaType, + rendererUrl: responseBody.meta.rendererUrl, + advertiserDomains: responseBody.meta.advertiserDomains, + advertiserId: responseBody.meta.advertiserId, + advertiserName: responseBody.meta.advertiserName, + agencyId: responseBody.meta.agencyId, + agencyName: responseBody.meta.agencyName, + brandId: responseBody.meta.brandId, + brandName: responseBody.meta.brandName, + dchain: responseBody.meta.dchain, + networkId: responseBody.meta.networkId, + networkName: responseBody.meta.networkName, + primaryCatId: responseBody.meta.primaryCatId, + secondaryCatIds: responseBody.meta.secondaryCatIds + } + }; + if (responseBody.vastUrl) { + bid.vastUrl = responseBody.vastUrl; + } else if (responseBody.vastXml) { + bid.vastXml = responseBody.vastXml; + } + + const bids = []; + + if (isBidResponseValid(bid)) { + bids.push(bid); + } else { + logError(BIDDER_CODE + ': server returns invalid response'); + } + + return bids; + } catch (e) { + logError(BIDDER_CODE + ': error parsing server response to Prebid format'); + return []; + } +}; + +function getUserSyncs (syncOptions, responses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + const pixelType = syncOptions.pixelEnabled ? 'image' : 'iframe'; + const urls = deepAccess(responses, '0.body.pixels') || COOKIE_SYNC_FALLBACK_URLS; + + return [].concat(urls).map(url => ({ + type: pixelType, + url: url + .replace('{{GDPR}}', gdprConsent ? (gdprConsent.gdprApplies ? '1' : '0') : '0') + .replace('{{GDPR_CONSENT}}', gdprConsent ? encodeURIComponent(gdprConsent.consentString) : '') + .replace('{{USP_CONSENT}}', uspConsent ? encodeURIComponent(uspConsent) : '') + })); + } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + gvlid: GVLID, + getUserSyncs, +}; + +registerBidder(spec); + +function getDomainWithoutSubdomain (hostname) { + const parts = hostname.split('.'); + const newParts = []; + for (let i = parts.length - 1; i >= 0; i--) { + newParts.push(parts[i]); + if (newParts.length !== 1 && parts[i].length > 3) { + break; + } + } + return newParts.reverse().join('.'); +} diff --git a/modules/vidoomyBidAdapter.md b/modules/vidoomyBidAdapter.md new file mode 100644 index 00000000000..d91ace5a7b9 --- /dev/null +++ b/modules/vidoomyBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +**Module Name:** Vidoomy Bid Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** support@vidoomy.com + +# Description + +Module to connect with Vidoomy, supporting banner and video + +# Test Parameters +For banner +```js +var adUnits = [ + { + code: 'test-ad', + mediaTypes: { + banner: { + sizes: [[300, 250]] // only first size will be accepted + } + }, + bids: [ + { + bidder: 'vidoomy', + params: { + id: '123123', + pid: '123123', + bidfloor: 0.5 // This is optional + } + } + ] + } +]; +``` + +For video +```js +var adUnits = [ + { + code: 'test-ad', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250] + } + }, + bids: [ + { + bidder: 'vidoomy', + params: { + id: '123123', + pid: '123123', + bidfloor: 0.5 // This is optional + } + } + ] + } +]; +``` diff --git a/modules/viewability.js b/modules/viewability.js new file mode 100644 index 00000000000..b12c53b7f59 --- /dev/null +++ b/modules/viewability.js @@ -0,0 +1,177 @@ +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'; + +export const MODULE_NAME = 'viewability'; + +export function init() { + (getGlobal()).viewability = { + startMeasurement: startMeasurement, + stopMeasurement: stopMeasurement, + }; + + listenMessagesFromCreative(); +} + +const observers = {}; + +function isValid(vid, element, tracker, criteria) { + if (!element) { + logWarn(`${MODULE_NAME}: no html element provided`); + return false; + } + + let validTracker = tracker && + ((tracker.method === 'img' && isStr(tracker.value)) || + (tracker.method === 'js' && isStr(tracker.value)) || + (tracker.method === 'callback' && isFn(tracker.value))); + + if (!validTracker) { + logWarn(`${MODULE_NAME}: invalid tracker`, tracker); + return false; + } + + if (!criteria || !criteria.inViewThreshold || !criteria.timeInView) { + logWarn(`${MODULE_NAME}: missing criteria`, criteria); + return false; + } + + if (!vid || observers[vid]) { + logWarn(`${MODULE_NAME}: must provide an unregistered vid`, vid); + return false; + } + + return true; +} + +function stopObserving(observer, vid, element) { + observer.unobserve(element); + observers[vid].done = true; +} + +function fireViewabilityTracker(element, tracker) { + switch (tracker.method) { + case 'img': + triggerPixel(tracker.value, () => { + logInfo(`${MODULE_NAME}: viewability pixel fired`, tracker.value); + }); + break; + case 'js': + insertHtmlIntoIframe(``); + break; + case 'callback': + tracker.value(element); + break; + } +} + +function viewabilityCriteriaMet(observer, vid, element, tracker) { + stopObserving(observer, vid, element); + fireViewabilityTracker(element, tracker); +} + +/** + * Start measuring viewability of an element + * @typedef {{ method: string='img','js','callback', value: string|function }} ViewabilityTracker { method: 'img', value: 'http://my.tracker/123' } + * @typedef {{ inViewThreshold: number, timeInView: number }} ViewabilityCriteria { inViewThreshold: 0.5, timeInView: 1000 } + * @param {string} vid unique viewability identifier + * @param {HTMLElement} element + * @param {ViewabilityTracker} tracker + * @param {ViewabilityCriteria} criteria + */ +export function startMeasurement(vid, element, tracker, criteria) { + if (!isValid(vid, element, tracker, criteria)) { + return; + } + + const options = { + root: null, + rootMargin: '0px', + threshold: criteria.inViewThreshold, + }; + + let observer; + let viewable = false; + let stateChange = (entries) => { + viewable = entries[0].isIntersecting; + + if (viewable) { + observers[vid].timeoutId = window.setTimeout(() => { + viewabilityCriteriaMet(observer, vid, element, tracker); + }, criteria.timeInView); + } else if (observers[vid].timeoutId) { + window.clearTimeout(observers[vid].timeoutId); + } + }; + + observer = new IntersectionObserver(stateChange, options); + observers[vid] = { + observer: observer, + element: element, + timeoutId: null, + done: false, + }; + + observer.observe(element); + + logInfo(`${MODULE_NAME}: startMeasurement called with:`, arguments); +} + +/** + * Stop measuring viewability of an element + * @param {string} vid unique viewability identifier + */ +export function stopMeasurement(vid) { + if (!vid || !observers[vid]) { + logWarn(`${MODULE_NAME}: must provide a registered vid`, vid); + return; + } + + observers[vid].observer.unobserve(observers[vid].element); + if (observers[vid].timeoutId) { + window.clearTimeout(observers[vid].timeoutId); + } + + // allow the observer under this vid to be created again + if (!observers[vid].done) { + delete observers[vid]; + } +} + +function listenMessagesFromCreative() { + window.addEventListener('message', receiveMessage, false); +} + +/** + * Recieve messages from creatives + * @param {MessageEvent} evt + */ +export function receiveMessage(evt) { + var key = evt.message ? 'message' : 'data'; + var data = {}; + try { + data = JSON.parse(evt[key]); + } catch (e) { + return; + } + + if (!data || data.message !== 'Prebid Viewability') { + return; + } + + switch (data.action) { + case 'startMeasurement': + let element = data.elementId && document.getElementById(data.elementId); + if (!element) { + element = find(document.getElementsByTagName('IFRAME'), iframe => (iframe.contentWindow || iframe.contentDocument.defaultView) == evt.source); + } + + startMeasurement(data.vid, element, data.tracker, data.criteria); + break; + case 'stopMeasurement': + stopMeasurement(data.vid); + break; + } +} + +init(); diff --git a/modules/viewability.md b/modules/viewability.md new file mode 100644 index 00000000000..df93b5c40db --- /dev/null +++ b/modules/viewability.md @@ -0,0 +1,87 @@ +# Overview + +Module Name: Viewability + +Purpose: Track when a given HTML element becomes viewable + +Maintainer: atrajkovic@magnite.com + +# Configuration + +Module does not need any configuration, as long as you include it in your PBJS bundle. +Viewability module has only two functions `startMeasurement` and `stopMeasurement` which can be used to enable more complex viewability measurements. Since it allows tracking from within creative (possibly inside a safe frame) this module registers a message listener, for messages with a format that is described bellow. + +## `startMeasurement` + +| startMeasurement Arg Object | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| vid | Required | String | Unique viewability identifier, used to reference particular observer | `"ae0f9"` | +| element | Required | HTMLElement | Reference to an HTML element that needs to be tracked | `document.getElementById('test_div')` | +| tracker | Required | ViewabilityTracker | How viewaility event is communicated back to the parties of interest | `{ method: 'img', value: 'http://my.tracker/123' }` | +| criteria | Required | ViewabilityCriteria| Defines custom viewability criteria using the threshold and duration provided | `{ inViewThreshold: 0.5, timeInView: 1000 }` | + +| ViewabilityTracker | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| method | Required | String | Type of method for Tracker | `'img' OR 'js' OR 'callback'` | +| value | Required | String | URL string for 'img' and 'js' Trackers, or a function for 'callback' Tracker | `'http://my.tracker/123'` | + +| ViewabilityCriteria | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| inViewThreshold | Required | Number | Represents a percentage threshold for the Element to be registered as in view | `0.5` | +| timeInView | Required | Number | Number of milliseconds that a given element needs to be in view continuously, above the threshold | `1000` | + +## Please Note: +- `vid` allows for multiple trackers, with different criteria to be registered for a given HTML element, independently. It's not autogenerated by `startMeasurement()`, it needs to be provided by the caller so that it doesn't have to be posted back to the source iframe (in case viewability is started from within the creative). +- In case of 'callback' method, HTML element is being passed back to the callback function. +- When a tracker needs to be started, without direct access to pbjs, postMessage mechanism can be used to invoke `startMeasurement`, with a following payload: `vid`, `tracker` and `criteria` as described above, but also with `message: 'Prebid Viewability'` and `action: 'startMeasurement'`. Optionally payload can provide `elementId`, if available at that time (for ad servers where name of the iframe is known, or adservers that render outside an iframe). If `elementId` is not provided, viewability module will try to find the iframe that corresponds to the message source. + + +## `stopMeasurement` + +| stopMeasurement Arg Object | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| vid | Required | String | Unique viewability identifier, referencing an already started viewability tracker. | `"ae0f9"` | + +## Please Note: +- When a tracker needs to be stopped, without direct access to pbjs, postMessage mechanism can be used here as well. To invoke `stopMeasurement`, you provide the payload with `vid`, `message: 'Prebid Viewability'` and `action: 'stopMeasurement`. Check the example bellow. + +# Examples + +## Example of starting a viewability measurement, when you have direct access to pbjs +``` +pbjs.viewability.startMeasurement( + 'ae0f9', + document.getElementById('test_div'), + { method: 'img', value: 'http://my.tracker/123' }, + { inViewThreshold: 0.5, timeInView: 1000 } +); +``` + +## Example of starting a viewability measurement from within a rendered creative +``` +let viewabilityRecord = { + vid: 'ae0f9', + tracker: { method: 'img', value: 'http://my.tracker/123'}, + criteria: { inViewThreshold: 0.5, timeInView: 1000 }, + message: 'Prebid Viewability', + action: 'startMeasurement' +} + +window.parent.postMessage(JSON.stringify(viewabilityRecord), '*'); +``` + +## Example of stopping the viewability measurement, when you have direct access to pbjs +``` +pbjs.viewability.stopMeasurement('ae0f9'); +``` + +## Example of stopping the viewability measurement from within a rendered creative +``` +let viewabilityRecord = { + vid: 'ae0f9', + message: 'Prebid Viewability', + action: 'stopMeasurement' +} + +window.parent.postMessage(JSON.stringify(viewabilityRecord), '*'); +``` diff --git a/modules/viewdeosDXBidAdapter.js b/modules/viewdeosDXBidAdapter.js index 22c16ab5b34..e3d02938c5b 100644 --- a/modules/viewdeosDXBidAdapter.js +++ b/modules/viewdeosDXBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { deepAccess, isArray, flatten, logError, parseSizesInput } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {VIDEO, BANNER} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; @@ -16,7 +16,7 @@ export const spec = { gvlid: 924, supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { - return !!utils.deepAccess(bid, 'params.aid'); + return !!deepAccess(bid, 'params.aid'); }, getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; @@ -43,9 +43,9 @@ export const spec = { } if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { - utils.isArray(serverResponses) && serverResponses.forEach((response) => { + isArray(serverResponses) && serverResponses.forEach((response) => { if (response.body) { - if (utils.isArray(response.body)) { + if (isArray(response.body)) { response.body.forEach(b => { addSyncs(b); }) @@ -81,12 +81,12 @@ export const spec = { serverResponse = serverResponse.body; let bids = []; - if (!utils.isArray(serverResponse)) { + if (!isArray(serverResponse)) { return parseRTBResponse(serverResponse, bidderRequest); } serverResponse.forEach(serverBidResponse => { - bids = utils.flatten(bids, parseRTBResponse(serverBidResponse, bidderRequest)); + bids = flatten(bids, parseRTBResponse(serverBidResponse, bidderRequest)); }); return bids; @@ -94,7 +94,7 @@ export const spec = { }; function parseRTBResponse(serverResponse, bidderRequest) { - const isInvalidValidResp = !serverResponse || !utils.isArray(serverResponse.bids); + const isInvalidValidResp = !serverResponse || !isArray(serverResponse.bids); const bids = []; @@ -102,7 +102,7 @@ function parseRTBResponse(serverResponse, bidderRequest) { const extMessage = serverResponse && serverResponse.ext && serverResponse.ext.message ? `: ${serverResponse.ext.message}` : ''; const errorMessage = `in response for ${bidderRequest.bidderCode} adapter ${extMessage}`; - utils.logError(errorMessage); + logError(errorMessage); return bids; } @@ -125,15 +125,15 @@ function parseRTBResponse(serverResponse, bidderRequest) { function bidToTag(bidRequests, bidderRequest) { const tag = { - domain: utils.deepAccess(bidderRequest, 'refererInfo.referer') + domain: deepAccess(bidderRequest, 'refererInfo.referer') }; - if (utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { tag.gdpr = 1; - tag.gdpr_consent = utils.deepAccess(bidderRequest, 'gdprConsent.consentString'); + tag.gdpr_consent = deepAccess(bidderRequest, 'gdprConsent.consentString'); } - if (utils.deepAccess(bidderRequest, 'bidderRequest.uspConsent')) { + if (deepAccess(bidderRequest, 'bidderRequest.uspConsent')) { tag.us_privacy = bidderRequest.uspConsent; } @@ -151,14 +151,14 @@ function bidToTag(bidRequests, bidderRequest) { * @returns {object} */ function prepareRTBRequestParams(_index, bid) { - const mediaType = utils.deepAccess(bid, 'mediaTypes.video') ? VIDEO : DISPLAY; + const mediaType = deepAccess(bid, 'mediaTypes.video') ? VIDEO : DISPLAY; const index = !_index ? '' : `${_index + 1}`; - const sizes = bid.sizes ? bid.sizes : (mediaType === VIDEO ? utils.deepAccess(bid, 'mediaTypes.video.playerSize') : utils.deepAccess(bid, 'mediaTypes.banner.sizes')); + const sizes = bid.sizes ? bid.sizes : (mediaType === VIDEO ? deepAccess(bid, 'mediaTypes.video.playerSize') : deepAccess(bid, 'mediaTypes.banner.sizes')); return { ['callbackId' + index]: bid.bidId, ['aid' + index]: bid.params.aid, ['ad_type' + index]: mediaType, - ['sizes' + index]: utils.parseSizesInput(sizes).join() + ['sizes' + index]: parseSizesInput(sizes).join() }; } @@ -168,8 +168,8 @@ function prepareRTBRequestParams(_index, bid) { * @returns {object} */ function getMediaType(bidderRequest) { - const videoMediaType = utils.deepAccess(bidderRequest, 'mediaTypes.video'); - const context = utils.deepAccess(bidderRequest, 'mediaTypes.video.context'); + const videoMediaType = deepAccess(bidderRequest, 'mediaTypes.video'); + const context = deepAccess(bidderRequest, 'mediaTypes.video.context'); return !videoMediaType ? DISPLAY : context === OUTSTREAM ? OUTSTREAM : VIDEO; } @@ -190,7 +190,10 @@ function createBid(bidResponse, mediaType, bidderParams) { cpm: bidResponse.cpm, netRevenue: true, mediaType, - ttl: 3600 + ttl: 3600, + meta: { + advertiserDomains: bidResponse.adomain || [] + } }; if (mediaType === DISPLAY) { diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index 63f4121724e..af8672ea233 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -1,22 +1,22 @@ -import * as utils from '../src/utils.js'; +import { triggerPixel, parseSizesInput, deepAccess, logError, getGptSlotInfoForAdUnitCode } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { INSTREAM as VIDEO_INSTREAM } from '../src/video.js'; -const { parseSizesInput, getKeys, logError, deepAccess } = utils; const BIDDER_CODE = 'visx'; +const GVLID = 154; const BASE_URL = 'https://t.visx.net'; -const ENDPOINT_URL = BASE_URL + '/hb'; +const DEBUG_URL = 'https://t-stage.visx.net'; +const ENDPOINT_PATH = '/hb_post'; const TIME_TO_LIVE = 360; const DEFAULT_CUR = 'EUR'; -const ADAPTER_SYNC_URL = BASE_URL + '/push_sync'; -const TRACK_WIN_URL = BASE_URL + '/track/win'; -const TRACK_PENDING_URL = BASE_URL + '/track/pending'; -const TRACK_TIMEOUT_URL = BASE_URL + '/track/bid_timeout'; +const ADAPTER_SYNC_PATH = '/push_sync'; +const TRACK_TIMEOUT_PATH = '/track/bid_timeout'; const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', noBid: 'Array of bid objects is empty', + noImpId: 'Bid from response has no impid parameter - ', noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', emptyUids: 'Uids should not be empty', emptySeatbid: 'Seatbid array from response has an empty item', @@ -29,14 +29,17 @@ const LOG_ERROR_MESS = { videoMissing: 'Bid request videoType property is missing - ' }; const currencyWhiteList = ['EUR', 'USD', 'GBP', 'PLN']; -const RE_EMPTY_OR_ONLY_COMMAS = /^,*$/; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { if (_isVideoBid(bid)) { - if (!_isValidVideoBid(bid)) { - return false; + if (!_isValidVideoBid(bid, true)) { + // in case if video bid configuration invalid will try to send bid request for banner + if (!_isBannerBid(bid)) { + return false; + } } } return !!bid.params.uid; @@ -44,89 +47,53 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { const auids = []; const bidsMap = {}; - const slotsMapByUid = {}; - const sizeMap = {}; const bids = validBidRequests || []; const currency = config.getConfig(`currency.bidderCurrencyDefault.${BIDDER_CODE}`) || config.getConfig('currency.adServerCurrency') || DEFAULT_CUR; + let reqId; let payloadSchain; let payloadUserId; - const videoTypes = _initVideoTypes(bids); + let payloadUserEids; + let timeout; if (currencyWhiteList.indexOf(currency) === -1) { logError(LOG_ERROR_MESS.notAllowedCurrency + currency); return; } + const imp = []; + bids.forEach(bid => { reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode, schain, userId} = bid; - auids.push(uid); + + const impObj = buildImpObject(bid); + if (impObj) { + imp.push(impObj); + bidsMap[bid.bidId] = bid; + } + + const { params: { uid }, schain, userId, userIdAsEids } = bid; + if (!payloadSchain && schain) { payloadSchain = schain; } - if (!payloadUserId && userId) { - payloadUserId = userId; + if (!payloadUserEids && userIdAsEids) { + payloadUserEids = userIdAsEids; } - const sizesId = parseSizesInput(bid.sizes); - if (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; - } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; - } else { - slotsMap[adUnitCode].bids.push(bid); + if (!payloadUserId && userId) { + payloadUserId = userId; } - const slot = slotsMap[adUnitCode]; - - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; - } - - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); - } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); + auids.push(uid); }); - const payload = { - pt: 'net', - auids: auids.join(','), - sizes: getKeys(sizeMap).join(','), - r: reqId, - cur: currency, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$', - ...videoTypes - }; - - if (payloadSchain) { - payload.schain = JSON.stringify(payloadSchain); - } - - if (payloadUserId) { - if (payloadUserId.tdid) { - payload.tdid = payloadUserId.tdid; - } - if (payloadUserId.id5id && payloadUserId.id5id.uid) { - payload.id5 = payloadUserId.id5id.uid; - } - if (payloadUserId.digitrustid && payloadUserId.digitrustid.data && payloadUserId.digitrustid.data.id) { - payload.dtid = payloadUserId.digitrustid.data.id; - } - } + const payload = {}; if (bidderRequest) { + timeout = bidderRequest.timeout; if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { payload.u = bidderRequest.refererInfo.referer; } @@ -140,19 +107,50 @@ export const spec = { } } + const bidderTimeout = Number(config.getConfig('bidderTimeout')) || timeout; + const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; + const source = { + ext: { + wrapperType: 'Prebid_js', + wrapperVersion: '$prebid.version$', + ...(payloadSchain && { schain: payloadSchain }) + } + }; + const user = { + ext: { + ...(payloadUserEids && { eids: payloadUserEids }), + ...(payload.gdpr_consent && { consent: payload.gdpr_consent }) + } + }; + const regs = ('gdpr_applies' in payload) && { + ext: { + gdpr: payload.gdpr_applies + } + }; + + const request = { + id: reqId, + imp, + tmax, + cur: [currency], + source, + site: { page: payload.u }, + ...(Object.keys(user.ext).length && { user }), + ...(regs && { regs }) + }; + return { - method: 'GET', - url: ENDPOINT_URL, - data: payload, - bidsMap: bidsMap, + method: 'POST', + url: buildUrl(ENDPOINT_PATH) + '?auids=' + encodeURIComponent(auids.join(',')), + data: request, + bidsMap }; }, interpretResponse: function(serverResponse, bidRequest) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; - const bidsWithoutSizeMatching = []; const bidsMap = bidRequest.bidsMap; - const currency = bidRequest.data.cur; + const currency = bidRequest.data.cur[0]; let errorMessage; @@ -163,46 +161,107 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, currency, bidResponses, bidsWithoutSizeMatching); - }); - bidsWithoutSizeMatching.forEach(serverBid => { - _addBidResponse(serverBid, bidsMap, currency, bidResponses); + _addBidResponse(_getBidFromResponse(respItem), bidsMap, currency, bidResponses); }); } if (errorMessage) logError(errorMessage); return bidResponses; }, getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { - if (syncOptions.pixelEnabled) { - var query = []; - if (gdprConsent) { - if (gdprConsent.consentString) { - query.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString)); - } - query.push('gdpr_applies=' + encodeURIComponent( - (typeof gdprConsent.gdprApplies === 'boolean') - ? Number(gdprConsent.gdprApplies) : 1)); + var query = []; + if (gdprConsent) { + if (gdprConsent.consentString) { + query.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString)); } + query.push('gdpr_applies=' + encodeURIComponent( + (typeof gdprConsent.gdprApplies === 'boolean') + ? Number(gdprConsent.gdprApplies) : 1)); + } + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: buildUrl(ADAPTER_SYNC_PATH) + '?iframe=1' + (query.length ? '&' + query.join('&') : '') + }]; + } else if (syncOptions.pixelEnabled) { return [{ type: 'image', - url: ADAPTER_SYNC_URL + (query.length ? '?' + query.join('&') : '') + url: buildUrl(ADAPTER_SYNC_PATH) + (query.length ? '?' + query.join('&') : '') }]; } }, onSetTargeting: function(bid) { // Call '/track/pending' with the corresponding bid.requestId - utils.triggerPixel(TRACK_PENDING_URL + '?requestId=' + bid.requestId); + if (bid.ext && bid.ext.events && bid.ext.events.pending) { + triggerPixel(bid.ext.events.pending); + } }, onBidWon: function(bid) { // Call '/track/win' with the corresponding bid.requestId - utils.triggerPixel(TRACK_WIN_URL + '?requestId=' + bid.requestId); + if (bid.ext && bid.ext.events && bid.ext.events.win) { + triggerPixel(bid.ext.events.win); + } }, onTimeout: function(timeoutData) { // Call '/track/bid_timeout' with timeout data - utils.triggerPixel(TRACK_TIMEOUT_URL + '?data=' + JSON.stringify(timeoutData)); + triggerPixel(buildUrl(TRACK_TIMEOUT_PATH) + '//' + JSON.stringify(timeoutData)); } }; +function buildUrl(path) { + return (config.getConfig('devMode') ? DEBUG_URL : BASE_URL) + path; +} + +function makeBanner(bannerParams) { + const bannerSizes = bannerParams && bannerParams.sizes; + if (bannerSizes) { + const sizes = parseSizesInput(bannerSizes); + if (sizes.length) { + const format = sizes.map(size => { + const [ width, height ] = size.split('x'); + const w = parseInt(width, 10); + const h = parseInt(height, 10); + return { w, h }; + }); + + return { format }; + } + } +} + +function makeVideo(videoParams = {}) { + const video = Object.keys(videoParams).filter((param) => param !== 'context' && param !== 'playerSize') + .reduce((result, param) => { + result[param] = videoParams[param]; + return result; + }, { w: deepAccess(videoParams, 'playerSize.0.0'), h: deepAccess(videoParams, 'playerSize.0.1') }); + + if (video.w && video.h) { + return video; + } +} + +function buildImpObject(bid) { + const { params: { uid }, bidId, mediaTypes, sizes, adUnitCode } = bid; + const video = mediaTypes && _isVideoBid(bid) && _isValidVideoBid(bid) && makeVideo(mediaTypes.video); + const banner = makeBanner((mediaTypes && mediaTypes.banner) || (!video && { sizes })); + const impObject = { + id: bidId, + ...(banner && { banner }), + ...(video && { video }), + ext: { + bidder: { uid: Number(uid) }, + } + }; + + if (impObject.banner) { + impObject.ext.bidder.adslotExists = _isAdSlotExists(adUnitCode); + } + + if (impObject.ext.bidder.uid && (impObject.banner || impObject.video)) { + return impObject; + } +} + function _getBidFromResponse(respItem) { if (!respItem) { logError(LOG_ERROR_MESS.emptySeatbid); @@ -214,61 +273,52 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidsMap, currency, bidResponses, bidsWithoutSizeMatching) { +function _addBidResponse(serverBid, bidsMap, currency, bidResponses) { if (!serverBid) return; let errorMessage; if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); + if (!serverBid.impid) errorMessage = LOG_ERROR_MESS.noImpId + JSON.stringify(serverBid); if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else { const reqCurrency = currency || DEFAULT_CUR; - const awaitingBids = bidsMap[serverBid.auid]; - if (awaitingBids) { + const bid = bidsMap[serverBid.impid]; + if (bid) { if (serverBid.cur && serverBid.cur !== reqCurrency) { errorMessage = LOG_ERROR_MESS.currencyMismatch + reqCurrency + ' - ' + serverBid.cur; } else { - const sizeId = bidsWithoutSizeMatching ? `${serverBid.w}x${serverBid.h}` : Object.keys(awaitingBids)[0]; - if (awaitingBids[sizeId]) { - const slot = awaitingBids[sizeId][0]; - - const bid = slot.bids.shift(); - const bidResponse = { - requestId: bid.bidId, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid, - currency: reqCurrency, - netRevenue: true, - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid - }; - - if (!_isVideoBid(bid)) { - bidResponse.ad = serverBid.adm; - } else { - bidResponse.vastXml = serverBid.adm; - bidResponse.mediaType = 'video'; - } - - bidResponses.push(bidResponse); - - if (!slot.bids.length) { - slot.parents.forEach(({parent, key, uid}) => { - const index = parent[key].indexOf(slot); - if (index > -1) { - parent[key].splice(index, 1); - } - if (!parent[key].length) { - delete parent[key]; - if (!getKeys(parent).length) { - delete bidsMap[uid]; - } - } - }); - } + const bidResponse = { + requestId: bid.bidId, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, + currency: reqCurrency, + netRevenue: true, + ttl: TIME_TO_LIVE, + dealId: serverBid.dealid, + meta: { + advertiserDomains: serverBid.advertiserDomains ? serverBid.advertiserDomains : [], + mediaType: serverBid.mediaType + }, + }; + + if (serverBid.ext && serverBid.ext.prebid) { + bidResponse.ext = serverBid.ext.prebid; + } + + const visxTargeting = deepAccess(serverBid, 'ext.prebid.targeting'); + if (visxTargeting) { + bidResponse.adserverTargeting = visxTargeting; + } + + if (!_isVideoInstreamBid(bid)) { + bidResponse.ad = serverBid.adm; } else { - bidsWithoutSizeMatching && bidsWithoutSizeMatching.push(serverBid); + bidResponse.vastXml = serverBid.adm; + bidResponse.mediaType = 'video'; } + + bidResponses.push(bidResponse); } } else { errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; @@ -283,80 +333,43 @@ function _isVideoBid(bid) { return bid.mediaType === VIDEO || deepAccess(bid, 'mediaTypes.video'); } -function _isValidVideoBid(bid) { +function _isVideoInstreamBid(bid) { + return _isVideoBid(bid) && deepAccess(bid, 'mediaTypes.video', {}).context === VIDEO_INSTREAM; +} + +function _isBannerBid(bid) { + return bid.mediaType === BANNER || deepAccess(bid, 'mediaTypes.banner'); +} + +function _isValidVideoBid(bid, logErrors = false) { let result = true; const videoMediaType = deepAccess(bid, 'mediaTypes.video'); - if (videoMediaType.context !== VIDEO_INSTREAM) { - logError(LOG_ERROR_MESS.onlyVideoInstream) + if (!_isVideoInstreamBid(bid)) { + if (logErrors) { + logError(LOG_ERROR_MESS.onlyVideoInstream); + } result = false; } if (!(videoMediaType.playerSize && parseSizesInput(deepAccess(videoMediaType, 'playerSize', [])))) { - logError(LOG_ERROR_MESS.videoMissing + 'playerSize'); - result = false; - } - if (!videoMediaType.mimes) { - logError(LOG_ERROR_MESS.videoMissing + 'mimes'); - result = false; - } - if (!videoMediaType.protocols) { - logError(LOG_ERROR_MESS.videoMissing + 'protocols'); + if (logErrors) { + logError(LOG_ERROR_MESS.videoMissing + 'playerSize'); + } result = false; } return result; } -function _initVideoTypes(bids) { - const result = {}; - let _playerSize = []; - let _protocols = []; - let _api = []; - let _mimes = []; - let _minduration = []; - let _maxduration = []; - let _skip = []; - if (bids && bids.length) { - bids.forEach(function (bid) { - const mediaTypes = deepAccess(bid, 'mediaTypes.video', {}); - _playerSize.push(parseSizesInput(deepAccess(mediaTypes, 'playerSize', [])).join('|')); - _protocols.push(deepAccess(mediaTypes, 'protocols', []).join('|')); - _api.push(deepAccess(mediaTypes, 'api', []).join('|')); - _mimes.push(deepAccess(mediaTypes, 'mimes', []).join('|')); - _minduration.push(deepAccess(mediaTypes, 'minduration', null)); - _maxduration.push(deepAccess(mediaTypes, 'maxduration', null)); - _skip.push(deepAccess(mediaTypes, 'skip', null)); - }); - } - _playerSize = _playerSize.join(','); - _protocols = _protocols.join(','); - _api = _api.join(','); - _mimes = _mimes.join(','); - _minduration = _minduration.join(','); - _maxduration = _maxduration.join(','); - _skip = _skip.join(','); - - if (!RE_EMPTY_OR_ONLY_COMMAS.test(_playerSize)) { - result.playerSize = _playerSize; - } - if (!RE_EMPTY_OR_ONLY_COMMAS.test(_protocols)) { - result.protocols = _protocols; - } - if (!RE_EMPTY_OR_ONLY_COMMAS.test(_api)) { - result.api = _api; +function _isAdSlotExists(adUnitCode) { + if (document.getElementById(adUnitCode)) { + return true; } - if (!RE_EMPTY_OR_ONLY_COMMAS.test(_mimes)) { - result.mimes = _mimes; - } - if (!RE_EMPTY_OR_ONLY_COMMAS.test(_minduration)) { - result.minduration = _minduration; - } - if (!RE_EMPTY_OR_ONLY_COMMAS.test(_maxduration)) { - result.maxduration = _maxduration; - } - if (!RE_EMPTY_OR_ONLY_COMMAS.test(_skip)) { - result.skip = _skip; + + const gptAdSlot = getGptSlotInfoForAdUnitCode(adUnitCode); + if (gptAdSlot && gptAdSlot.divId && document.getElementById(gptAdSlot.divId)) { + return true; } - return result; + return false; } registerBidder(spec); diff --git a/modules/visxBidAdapter.md b/modules/visxBidAdapter.md index 9578f7cc4a7..34ebe9bb937 100644 --- a/modules/visxBidAdapter.md +++ b/modules/visxBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: YOC VIS.X Bidder Adapter Module Type: Bidder Adapter -Maintainer: service@yoc.com +Maintainer: supply.partners@yoc.com ``` # Description @@ -47,16 +47,14 @@ var adUnits = [ } ] }, - // YOC In-stream adUnit + // In-stream video adUnit { code: 'instream-test-div', mediaTypes: { video: { context: 'instream', - playerSize: [400, 300], - mimes: ['video/mp4'], - protocols: [3, 6] - }, + playerSize: [400, 300] + } }, bids: [ { diff --git a/modules/vlybyBidAdapter.js b/modules/vlybyBidAdapter.js new file mode 100644 index 00000000000..10352179044 --- /dev/null +++ b/modules/vlybyBidAdapter.js @@ -0,0 +1,71 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js' + +import { BANNER, VIDEO } from '../src/mediaTypes.js' + +const ENDPOINT = '//prebid.vlyby.com/'; +const BIDDER_CODE = 'vlyby'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO, BANNER], + + isBidRequestValid: function (bid) { + if (bid && bid.params && bid.params.publisherId) { + return true + } + return false + }, + + buildRequests: function (validBidRequests, bidderRequest = {}) { + const gdprConsent = bidderRequest.gdprConsent || {}; + return { + method: 'POST', + url: `${ENDPOINT}`, + data: { + request: { + auctionId: bidderRequest.auctionId + }, + gdprConsent: { + consentString: gdprConsent.consentString, + gdprApplies: gdprConsent.gdprApplies + }, + bidRequests: validBidRequests.map(({ params, sizes, bidId, adUnitCode }) => ({ + bidId, + adUnitCode, + params, + sizes + })) + }, + options: { + withCredentials: false, + contentType: 'application/json' + }, + validBidRequests: validBidRequests, + } + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + if (serverResponse.body) { + const vHB = serverResponse.body.bids; + try { + let bidResponse = { + requestId: vHB.bid, + cpm: vHB.cpm, + width: vHB.size.width, + height: vHB.size.height, + creativeId: vHB.creative.id, + currency: 'EUR', + netRevenue: true, + ttl: 360, + ad: vHB.creative.ad, + meta: { + adomain: vHB.adomain && Array.isArray(vHB.adomain) ? vHB.adomain : [] + } + }; + bidResponses.push(bidResponse); + } catch (e) { } + } + return bidResponses; + } +}; +registerBidder(spec); diff --git a/modules/vlybyBidAdapter.md b/modules/vlybyBidAdapter.md new file mode 100755 index 00000000000..41d9bbf8709 --- /dev/null +++ b/modules/vlybyBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: VLYBY Prebid Outstream +Module Type: Bidder Adapter +Tech-Support: prebid@vlyby.com +Client-Support: support@vlyby.com +``` + +# Description + +VLYBY Digital GmbH provides with this VLYBY Prebid Adapter a Mediation for the Outstream Product. Please contact support@vlyby.com for additional information and access to VLYBY User Interface and Prebid IDs. + +# Demo Implementation + +In most of the cases a Publisher will use his own AdServer for delivering Creatives to a Publisher-Website. This GPT implementation is only a skeleton. You need an additional Line-Item in your AdServer, Prebid Creative, access to VLYBY UI. + +### GPT Implementation +```javascript + var adUnits = [{ + code: '/your-network-id/adunit', + mediaTypes: { + banner: { + sizes: div_1_sizes + } + }, + bids: [{ + bidder: 'vlyby', + params: { + publisherId: 'f363eb2b75459b34592cc4', // needed - only demo + siteId: 'techpreview.vlyby_prebidadapter', // needed - only demo + placement:'default' // optional - provided by VLYBY UI + } + }] + }] +``` \ No newline at end of file diff --git a/modules/vmgBidAdapter.js b/modules/vmgBidAdapter.js deleted file mode 100644 index 57a812ec466..00000000000 --- a/modules/vmgBidAdapter.js +++ /dev/null @@ -1,86 +0,0 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -const BIDDER_CODE = 'vmg'; -const ENDPOINT = 'https://predict.vmg.nyc'; - -export const spec = { - code: BIDDER_CODE, - /** - * Determines whether or not the given bid request is valid. - * - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function (bidRequest) { - if (typeof bidRequest !== 'undefined') { - return true; - } else { - return false; - } - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function (validBidRequests, bidderRequest) { - let bidRequests = []; - let referer = window.location.href; - try { - referer = typeof bidderRequest.refererInfo === 'undefined' - ? window.top.location.href - : bidderRequest.refererInfo.referer; - } catch (e) {} - - validBidRequests.forEach(function(validBidRequest) { - bidRequests.push({ - adUnitCode: validBidRequest.adUnitCode, - referer: referer, - bidId: validBidRequest.bidId - }); - }); - - return { - method: 'POST', - url: ENDPOINT, - data: JSON.stringify(bidRequests) - }; - }, - /** - * Unpack the response from the server into a list of bids. - * - * Some required bid params are not needed for this so default - * values are used. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, bidRequest) { - const validBids = JSON.parse(bidRequest.data); - let bidResponses = []; - if (typeof serverResponse.body !== 'undefined') { - const deals = serverResponse.body; - validBids.forEach(function(validBid) { - if (typeof deals[validBid.adUnitCode] !== 'undefined') { - const bidResponse = { - requestId: validBid.bidId, - ad: '
', - cpm: 0.01, - width: 0, - height: 0, - dealId: deals[validBid.adUnitCode], - ttl: 300, - creativeId: '1', - netRevenue: '0', - currency: 'USD' - }; - - bidResponses.push(bidResponse); - } - }); - } - - return bidResponses; - } -} - -registerBidder(spec); diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js index 73df9bb8b9b..8db97800630 100644 --- a/modules/voxBidAdapter.js +++ b/modules/voxBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js' +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'; @@ -11,7 +11,7 @@ const VIDEO_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVi const TTL = 60; function buildBidRequests(validBidRequests) { - return utils._map(validBidRequests, function(validBidRequest) { + return _map(validBidRequests, function(validBidRequest) { const params = validBidRequest.params; const bidRequest = { bidId: validBidRequest.bidId, @@ -53,7 +53,7 @@ const createRenderer = (bid) => { try { renderer.setRender(outstreamRender); } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); + logWarn('Prebid Error calling setRender on renderer', err); } return renderer; @@ -70,7 +70,10 @@ function buildBid(bidData) { netRevenue: true, mediaType: BANNER, ttl: TTL, - content: bidData.content + content: bidData.content, + meta: { + advertiserDomains: bidData.advertiserDomains || [], + } }; if (bidData.placement === 'video') { @@ -108,8 +111,8 @@ function hasVideoMandatoryParams(mediaTypes) { const isHasVideoContext = !!mediaTypes.video && (mediaTypes.video.context === 'instream' || mediaTypes.video.context === 'outstream'); const isPlayerSize = - !!utils.deepAccess(mediaTypes, 'video.playerSize') && - utils.isArray(utils.deepAccess(mediaTypes, 'video.playerSize')); + !!deepAccess(mediaTypes, 'video.playerSize') && + isArray(deepAccess(mediaTypes, 'video.playerSize')); return isHasVideoContext && isPlayerSize; } @@ -228,8 +231,8 @@ export const spec = { let bidRequests = JSON.parse(bidRequest.data).bidRequests; const serverBody = serverResponse.body; - if (serverBody && serverBody.bids && utils.isArray(serverBody.bids)) { - return utils._map(serverBody.bids, function(bid) { + if (serverBody && serverBody.bids && isArray(serverBody.bids)) { + return _map(serverBody.bids, function(bid) { let rawBid = find(bidRequests, function (item) { return item.bidId === bid.bidId; }); diff --git a/modules/vrtcalBidAdapter.js b/modules/vrtcalBidAdapter.js index ff24803f688..d08ef52106e 100644 --- a/modules/vrtcalBidAdapter.js +++ b/modules/vrtcalBidAdapter.js @@ -1,6 +1,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; +import {isFn, isPlainObject} from '../src/utils.js'; export const spec = { code: 'vrtcal', @@ -10,8 +11,17 @@ export const spec = { }, buildRequests: function (bidRequests) { const requests = bidRequests.map(function (bid) { - const params = { + let floor = 0; + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ currency: 'USD', mediaType: 'banner', size: bid.sizes.map(([w, h]) => ({w, h})) }); + + if (isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + const params = { prebidJS: 1, prebidAdUnitCode: bid.adUnitCode, id: bid.bidId, @@ -19,7 +29,7 @@ export const spec = { id: '1', banner: { }, - bidfloor: 0.75 + bidfloor: floor }], site: { id: 'VRTCAL_FILLED', @@ -70,6 +80,12 @@ export const spec = { nurl: response.seatbid[0].bid[0].nurl }; + if (response.seatbid[0].bid[0].adomain && response.seatbid[0].bid[0].adomain.length) { + bidResponse.meta = { + advertiserDomains: response.seatbid[0].bid[0].adomain + }; + } + bidResponses.push(bidResponse); } return bidResponses; diff --git a/modules/vubleBidAdapter.js b/modules/vubleBidAdapter.js deleted file mode 100644 index d14f24e9993..00000000000 --- a/modules/vubleBidAdapter.js +++ /dev/null @@ -1,198 +0,0 @@ -// Vuble Adapter - -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; - -const BIDDER_CODE = 'vuble'; - -const ENVS = ['com', 'net']; -const CURRENCIES = { - com: 'EUR', - net: 'USD' -}; -const TTL = 60; - -const outstreamRender = bid => { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - sizes: [bid.width, bid.height], - targetId: bid.adUnitCode, - adResponse: bid.adResponse, - rendererOptions: { - showBigPlayButton: false, - showProgressBar: 'bar', - showVolume: false, - allowFullscreen: true, - skippable: false, - } - }); - }); -} - -const createRenderer = (bid, serverResponse) => { - const renderer = Renderer.install({ - id: serverResponse.renderer_id, - url: serverResponse.renderer_url, - loaded: false, - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); - } - - return renderer; -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: ['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) { - let rawSizes = utils.deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes; - if (utils.isEmpty(rawSizes) || utils.parseSizesInput(rawSizes).length == 0) { - return false; - } - - if (!utils.deepAccess(bid, 'mediaTypes.video.context')) { - return false; - } - - if (!utils.contains(ENVS, bid.params.env)) { - return false; - } - - return !!(bid.params.env && bid.params.pubId && bid.params.zoneId); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function (validBidRequests, bidderRequest) { - return validBidRequests.map(bidRequest => { - // We take the first size - let rawSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') || bidRequest.sizes; - let size = utils.parseSizesInput(rawSize)[0].split('x'); - - // Get the page's url - let referer = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.referer : ''; - if (bidRequest.params.referrer) { - referer = bidRequest.params.referrer; - } - - // Get Video Context - let context = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); - - let url = 'https://player.mediabong.' + bidRequest.params.env + '/prebid/request'; - let data = { - width: size[0], - height: size[1], - pub_id: bidRequest.params.pubId, - zone_id: bidRequest.params.zoneId, - context: context, - floor_price: bidRequest.params.floorPrice ? bidRequest.params.floorPrice : 0, - url: referer, - env: bidRequest.params.env, - bid_id: bidRequest.bidId, - adUnitCode: bidRequest.adUnitCode - }; - - if (bidderRequest && bidderRequest.gdprConsent) { - data.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - gdpr_applies: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true - } - } - - return { - method: 'POST', - url: url, - data: data - }; - }); - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, bidRequest) { - const responseBody = serverResponse.body; - - if (typeof responseBody !== 'object' || responseBody.status !== 'ok') { - return []; - } - - let bids = []; - let bid = { - requestId: bidRequest.data.bid_id, - cpm: responseBody.cpm, - width: bidRequest.data.width, - height: bidRequest.data.height, - ttl: TTL, - creativeId: responseBody.creativeId, - dealId: responseBody.dealId, - netRevenue: true, - currency: CURRENCIES[bidRequest.data.env], - vastUrl: responseBody.url, - mediaType: 'video' - }; - - if (responseBody.renderer_url) { - let adResponse = { - ad: { - video: { - content: responseBody.content - } - } - }; - - Object.assign(bid, { - adResponse: adResponse, - adUnitCode: bidRequest.data.adUnitCode, - renderer: createRenderer(bid, responseBody) - }); - } - - bids.push(bid); - - return bids; - }, - - /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function (syncOptions, serverResponses) { - if (syncOptions.iframeEnabled) { - if (serverResponses.length > 0) { - let responseBody = serverResponses[0].body; - if (typeof responseBody !== 'object' || responseBody.iframeSync) { - return [{ - type: 'iframe', - url: responseBody.iframeSync - }]; - } - } - } - return []; - } -}; - -registerBidder(spec); diff --git a/modules/vuukleBidAdapter.js b/modules/vuukleBidAdapter.js index e9770b5e62e..ddf96b3e940 100644 --- a/modules/vuukleBidAdapter.js +++ b/modules/vuukleBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { parseSizesInput } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'vuukle'; @@ -14,7 +14,7 @@ export const spec = { buildRequests: function(bidRequests) { const requests = bidRequests.map(function (bid) { - const parseSized = utils.parseSizesInput(bid.sizes); + const parseSized = parseSizesInput(bid.sizes); const arrSize = parseSized[0].split('x'); const params = { url: encodeURIComponent(window.location.href), @@ -55,8 +55,12 @@ export const spec = { currency: res.currency || 'USD', netRevenue: true, ttl: TIME_TO_LIVE, - ad: res.ad + ad: res.ad, + meta: { + advertiserDomains: Array.isArray(res.adomain) ? res.adomain : [] + } }; + return [bidResponse]; }, } diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js index 1558ea39cd1..ee17a71dd35 100644 --- a/modules/waardexBidAdapter.js +++ b/modules/waardexBidAdapter.js @@ -1,7 +1,7 @@ +import { logError, isArray, deepAccess, getBidIdParameter } 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 * as utils from '../src/utils.js'; import find from 'core-js-pure/features/array/find.js'; const ENDPOINT = `https://hb.justbidit.xyz:8843/prebid`; @@ -9,38 +9,38 @@ const BIDDER_CODE = 'waardex'; const isBidRequestValid = bid => { if (!bid.bidId) { - utils.logError(BIDDER_CODE + ': bid.bidId should be non-empty'); + logError(BIDDER_CODE + ': bid.bidId should be non-empty'); return false; } if (!bid.params) { - utils.logError(BIDDER_CODE + ': bid.params should be non-empty'); + logError(BIDDER_CODE + ': bid.params should be non-empty'); return false; } if (!+bid.params.zoneId) { - utils.logError(BIDDER_CODE + ': bid.params.zoneId should be non-empty Number'); + logError(BIDDER_CODE + ': bid.params.zoneId should be non-empty Number'); return false; } if (bid.mediaTypes && bid.mediaTypes.video) { if (!bid.mediaTypes.video.playerSize) { - utils.logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be non-empty'); + logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be non-empty'); return false; } - if (!utils.isArray(bid.mediaTypes.video.playerSize)) { - utils.logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be an Array'); + if (!isArray(bid.mediaTypes.video.playerSize)) { + logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be an Array'); return false; } if (!bid.mediaTypes.video.playerSize[0]) { - utils.logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be non-empty'); + logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be non-empty'); return false; } - if (!utils.isArray(bid.mediaTypes.video.playerSize[0])) { - utils.logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be non-empty Array'); + if (!isArray(bid.mediaTypes.video.playerSize[0])) { + logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be non-empty Array'); return false; } } @@ -95,7 +95,7 @@ const getBidRequestsToSend = validBidRequests => { const getBidRequestToSend = validBidRequest => { const result = { bidId: validBidRequest.bidId, - bidfloor: parseFloat(validBidRequest.params.bidfloor) || 0, + bidfloor: 0, position: parseInt(validBidRequest.params.position) || 1, instl: parseInt(validBidRequest.params.instl) || 0, }; @@ -139,22 +139,22 @@ const transformSizes = requestSizes => { const createVideoObject = (videoMediaTypes, videoParams) => { return { - w: utils.deepAccess(videoMediaTypes, 'playerSize')[0][0], - h: utils.deepAccess(videoMediaTypes, 'playerSize')[0][1], - mimes: utils.getBidIdParameter('mimes', videoParams) || ['application/javascript', 'video/mp4', 'video/webm'], - minduration: utils.getBidIdParameter('minduration', videoParams) || 0, - maxduration: utils.getBidIdParameter('maxduration', videoParams) || 500, - protocols: utils.getBidIdParameter('protocols', videoParams) || [2, 3, 5, 6], - startdelay: utils.getBidIdParameter('startdelay', videoParams) || 0, - placement: utils.getBidIdParameter('placement', videoParams) || videoMediaTypes.context === 'outstream' ? 3 : 1, - skip: utils.getBidIdParameter('skip', videoParams) || 1, - skipafter: utils.getBidIdParameter('skipafter', videoParams) || 0, - minbitrate: utils.getBidIdParameter('minbitrate', videoParams) || 0, - maxbitrate: utils.getBidIdParameter('maxbitrate', videoParams) || 3500, - delivery: utils.getBidIdParameter('delivery', videoParams) || [2], - playbackmethod: utils.getBidIdParameter('playbackmethod', videoParams) || [1, 2, 3, 4], - api: utils.getBidIdParameter('api', videoParams) || [2], - linearity: utils.getBidIdParameter('linearity', videoParams) || 1 + w: deepAccess(videoMediaTypes, 'playerSize')[0][0], + h: deepAccess(videoMediaTypes, 'playerSize')[0][1], + mimes: getBidIdParameter('mimes', videoParams) || ['application/javascript', 'video/mp4', 'video/webm'], + minduration: getBidIdParameter('minduration', videoParams) || 0, + maxduration: getBidIdParameter('maxduration', videoParams) || 500, + protocols: getBidIdParameter('protocols', videoParams) || [2, 3, 5, 6], + startdelay: getBidIdParameter('startdelay', videoParams) || 0, + placement: getBidIdParameter('placement', videoParams) || videoMediaTypes.context === 'outstream' ? 3 : 1, + skip: getBidIdParameter('skip', videoParams) || 1, + skipafter: getBidIdParameter('skipafter', videoParams) || 0, + minbitrate: getBidIdParameter('minbitrate', videoParams) || 0, + maxbitrate: getBidIdParameter('maxbitrate', videoParams) || 3500, + delivery: getBidIdParameter('delivery', videoParams) || [2], + playbackmethod: getBidIdParameter('playbackmethod', videoParams) || [1, 2, 3, 4], + api: getBidIdParameter('api', videoParams) || [2], + linearity: getBidIdParameter('linearity', videoParams) || 1 }; }; diff --git a/modules/waardexBidAdapter.md b/modules/waardexBidAdapter.md index 8eade8409f4..6978281d40e 100644 --- a/modules/waardexBidAdapter.md +++ b/modules/waardexBidAdapter.md @@ -33,7 +33,6 @@ var adUnits = [ { bidder: 'waardex', params: { - bidfloor: 1.5, position: 1, instl: 1, zoneId: 1 @@ -60,31 +59,30 @@ const adUnits = [ mediaTypes: { video: { context: 'instream', - playerSize: sizes[0] + playerSize: sizes[0], + mimes: ['video/x-ms-wmv', 'video/mp4'], + minduration: 2, + maxduration: 10, + protocols: ['VAST 1.0', 'VAST 2.0'], + startdelay: -1, + placement: 1, + skip: 1, + skipafter: 2, + minbitrate: 0, + maxbitrate: 0, + delivery: [1, 2, 3], + playbackmethod: [1, 2], + api: [1, 2, 3, 4, 5, 6], + linearity: 1, + position: 1 } }, bids: [ { bidder: 'waardex', params: { - bidfloor: 1.5, - position: 1, instl: 1, - zoneId: 1, - mimes: ['video/x-ms-wmv', 'video/mp4'], - minduration: 2, - maxduration: 10, - protocols: ['VAST 1.0', 'VAST 2.0'], - startdelay: -1, - placement: 1, - skip: 1, - skipafter: 2, - minbitrate: 0, - maxbitrate: 0, - delivery: [1, 2, 3], - playbackmethod: [1, 2], - api: [1, 2, 3, 4, 5, 6], - linearity: 1, + zoneId: 1 } } ] diff --git a/modules/weboramaBidAdapter.md b/modules/weboramaBidAdapter.md deleted file mode 100644 index 5bdca0bfcd1..00000000000 --- a/modules/weboramaBidAdapter.md +++ /dev/null @@ -1,27 +0,0 @@ -# Overview - -``` -Module Name: Weborama SSP Bidder Adapter -Module Type: Bidder Adapter -Maintainer: devweborama@gmail.com -``` - -# Description - -Module that connects to Weborama SSP demand sources - -# Test Parameters -``` - var adUnits = [{ - code: 'placementCode', - sizes: [[300, 250]], - bids: [{ - bidder: 'weborama', - params: { - placementId: 0, - traffic: 'banner' - } - }] - } - ]; -``` diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js new file mode 100644 index 00000000000..ae9c22928e8 --- /dev/null +++ b/modules/weboramaRtdProvider.js @@ -0,0 +1,619 @@ +/** + * This module adds Weborama provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch contextual data (page-centric) from Weborama server + * and may access user-centric data from local storage + * @module modules/weboramaRtdProvider + * @requires module:modules/realTimeData + */ + +/** onData callback type + * @callback dataCallback + * @param {Object} data profile data + * @param {Boolean} site true if site, else it is user + * @returns {void} + */ + +/** + * @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 {?dataCallback} onData callback + * @property {?WeboCtxConf} weboCtxConf + * @property {?WeboUserDataConf} weboUserDataConf + */ + +/** + * @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 {?dataCallback} onData callback + * @property {?object} defaultProfile to be used if the profile is not found + * @property {?Boolean} enabled if false, will ignore this configuration + */ + +/** + * @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 {?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 + */ + +import { + getGlobal +} from '../src/prebidGlobal.js'; +import { + deepSetValue, + deepAccess, + isEmpty, + mergeDeep, + logError, + logWarn, + tryAppendQueryString, + logMessage, + isFn +} from '../src/utils.js'; +import { + submodule +} from '../src/hook.js'; +import { + ajax +} from '../src/ajax.js'; +import { + getStorageManager +} from '../src/storageManager.js'; + +const adapterManager = require('../src/adapterManager.js').default; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +/** @type {string} */ +const SUBMODULE_NAME = 'weborama'; +/** @type {string} */ +export const DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY = 'webo_wam2gam_entry'; +/** @type {string} */ +const LOCAL_STORAGE_USER_TARGETING_SECTION = 'targeting'; +/** @type {number} */ +const GVLID = 284; +/** @type {object} */ +export const storage = getStorageManager(GVLID, SUBMODULE_NAME); + +/** @type {null|Object} */ +let _weboContextualProfile = null; + +/** @type {Boolean} */ +let _weboCtxInitialized = false; + +/** @type {null|Object} */ +let _weboUserDataUserProfile = null; + +/** @type {Boolean} */ +let _weboUserDataInitialized = 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; + + return false + } + + normalizeConf(moduleParams, weboCtxConf); + + _weboCtxInitialized = false; + _weboContextualProfile = null; + + if (!weboCtxConf.token) { + logWarn('missing param "token" for weborama contextual sub module initialization'); + return false; + } + + logMessage('weborama contextual intialized with success'); + + return true; +} + +/** Initialize weboUserData sub module + * @param {ModuleParams} moduleParams + * @param {WeboUserDataConf} weboUserDataConf + * @return {Boolean} true if sub module was initialized with success + */ +function initWeboUserData(moduleParams, weboUserDataConf) { + if (!weboUserDataConf || weboUserDataConf.enabled === false) { + moduleParams.weboUserDataConf = null; + + return false; + } + + normalizeConf(moduleParams, weboUserDataConf); + + _weboUserDataInitialized = false; + _weboUserDataUserProfile = null; + + let message = 'weborama user-centric intialized with success'; + if (weboUserDataConf.hasOwnProperty('accountId')) { + message = `weborama user-centric intialized with success for account: ${weboUserDataConf.accountId}`; + } + + logMessage(message); + + return true; +} + +/** @type {Object} */ +const globalDefaults = { + setPrebidTargeting: true, + sendToBidders: true, + onData: (data, kind, def) => logMessage('onData(data,kind,default)', data, kind, def), +} + +/** normalize submodule configuration + * @param {ModuleParams} moduleParams + * @param {WeboCtxConf|WeboUserDataConf} submoduleParams + * @return {void} + */ +function normalizeConf(moduleParams, submoduleParams) { + Object.entries(globalDefaults).forEach(([propertyName, globalDefaultValue]) => { + if (!submoduleParams.hasOwnProperty(propertyName)) { + const hasModuleParam = moduleParams.hasOwnProperty(propertyName); + submoduleParams[propertyName] = (hasModuleParam) ? moduleParams[propertyName] : globalDefaultValue; + } + }) +} + +/** function that provides ad server targeting data to RTD-core + * @param {Array} adUnitsCodes + * @param {Object} moduleConfig + * @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; + + try { + const profile = getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting); + + if (isEmpty(profile)) { + return {}; + } + + const td = adUnitsCodes.reduce((data, adUnitCode) => { + if (adUnitCode) { + data[adUnitCode] = profile; + } + return data; + }, {}); + + return td; + } catch (e) { + logError('unable to format weborama rtd targeting data', e); + return {}; + } +} + +/** function that provides complete profile formatted to be used + * @param {ModuleParams} moduleParams + * @param {Boolean} weboCtxConfTargeting + * @param {Boolean} weboUserDataConfTargeting + * @returns {Object} complete profile + */ +function getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting) { + const profile = {}; + + if (weboCtxConfTargeting) { + const contextualProfile = getContextualProfile(moduleParams.weboCtxConf || {}); + mergeDeep(profile, contextualProfile); + } + + if (weboUserDataConfTargeting) { + const weboUserDataProfile = getWeboUserDataProfile(moduleParams.weboUserDataConf || {}); + mergeDeep(profile, weboUserDataProfile); + } + + return profile; +} + +/** return contextual profile + * @param {WeboCtxConf} weboCtxConf + * @returns {Object} contextual profile + */ +function getContextualProfile(weboCtxConf) { + const defaultContextualProfile = weboCtxConf.defaultProfile || {}; + return _weboContextualProfile || defaultContextualProfile; +} + +/** return weboUserData profile + * @param {WeboUserDataConf} weboUserDataConf + * @returns {Object} weboUserData profile + */ +function getWeboUserDataProfile(weboUserDataConf) { + const weboUserDataDefaultUserProfile = weboUserDataConf.defaultProfile || {}; + + if (storage.localStorageIsEnabled() && !_weboUserDataUserProfile) { + const localStorageProfileKey = weboUserDataConf.localStorageProfileKey || DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY; + + const entry = storage.getDataFromLocalStorage(localStorageProfileKey); + if (entry) { + const data = JSON.parse(entry); + if (data && Object.keys(data).length > 0) { + _weboUserDataUserProfile = data[LOCAL_STORAGE_USER_TARGETING_SECTION]; + } + } + } + + return _weboUserDataUserProfile || weboUserDataDefaultUserProfile; +} + +/** function that will allow RTD sub-modules to modify the AdUnit object for each auction + * @param {Object} reqBidsConfigObj + * @param {doneCallback} onDone + * @param {Object} moduleConfig + * @returns {void} + */ +export function getBidRequestData(reqBidsConfigObj, onDone, moduleConfig) { + moduleConfig = moduleConfig || {}; + const moduleParams = moduleConfig.params || {}; + const weboCtxConf = moduleParams.weboCtxConf || {}; + + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + + if (!_weboCtxInitialized) { + handleBidRequestData(adUnits, moduleParams); + + onDone(); + + return; + } + + fetchContextualProfile(weboCtxConf, (data) => { + logMessage('fetchContextualProfile on getBidRequestData is done'); + + setWeboContextualProfile(data); + }, () => { + handleBidRequestData(adUnits, moduleParams); + + onDone(); + }); +} + +/** function that handles bid request data + * @param {Object[]} adUnits + * @param {ModuleParams} moduleParams + * @returns {void} + */ + +function handleBidRequestData(adUnits, moduleParams) { + const weboCtxConf = moduleParams.weboCtxConf || {}; + const weboUserDataConf = moduleParams.weboUserDataConf || {}; + const weboCtxConfTargeting = weboCtxConf.sendToBidders; + const weboUserDataConfTargeting = weboUserDataConf.sendToBidders; + + if (weboCtxConfTargeting) { + const contextualProfile = getContextualProfile(weboCtxConf); + if (!isEmpty(contextualProfile)) { + setBidRequestProfile(adUnits, contextualProfile, true); + } + } + + if (weboUserDataConfTargeting) { + const weboUserDataProfile = getWeboUserDataProfile(weboUserDataConf); + if (!isEmpty(weboUserDataProfile)) { + setBidRequestProfile(adUnits, weboUserDataProfile, false); + } + } + + handleOnData(weboCtxConf, weboUserDataConf); +} + +/** function that handle with onData callbacks + * @param {WeboCtxConf} weboCtxConf + * @param {WeboUserDataConf} weboUserDataConf + */ + +function handleOnData(weboCtxConf, weboUserDataConf) { + const callbacks = [{ + onData: weboCtxConf.onData, + fetchData: () => getContextualProfile(weboCtxConf), + site: true, + }, { + onData: weboUserDataConf.onData, + fetchData: () => getWeboUserDataProfile(weboUserDataConf), + site: false, + }]; + + callbacks.filter(obj => isFn(obj.onData)).forEach(obj => { + try { + const data = obj.fetchData(); + obj.onData(data, obj.site); + } catch (e) { + const kind = (obj.site) ? 'site' : 'user'; + logError(`error while executure onData callback with ${kind}-based data:`, e); + } + }); +} + +/** function that set bid request data on each segment (site or user centric) + * @param {Object[]} adUnits + * @param {Object} profile + * @param {Boolean} site true if site centric, else it is user centric + * @returns {void} + */ +function setBidRequestProfile(adUnits, profile, site) { + setGlobalOrtb2(profile, site); + + adUnits.forEach(adUnit => { + if (adUnit.hasOwnProperty('bids')) { + const adUnitCode = adUnit.code || 'no code'; + adUnit.bids.forEach(bid => handleBid(adUnitCode, profile, site, bid)); + } + }); +} + +/** @type {string} */ +const APPNEXUS = 'appnexus'; + +/** @type {string} */ +const PUBMATIC = 'pubmatic'; + +/** @type {string} */ +const RUBICON = 'rubicon'; + +/** @type {string} */ +const SMARTADSERVER = 'smartadserver'; + +/** @type {Object} */ +const bidderAliasRegistry = adapterManager.aliasRegistry || {}; + +/** handle individual bid + * @param {string} adUnitCode + * @param {Object} profile + * @param {Boolean} site true if site centric, else it is user centric + * @param {Object} bid + * @returns {void} + */ +function handleBid(adUnitCode, profile, site, bid) { + const bidder = bidderAliasRegistry[bid.bidder] || bid.bidder; + + logMessage(`handling on adunit '${adUnitCode}', bidder '${bidder}' and bid`, bid); + + switch (bidder) { + case APPNEXUS: + handleAppnexusBid(profile, bid); + + break; + + case PUBMATIC: + handlePubmaticBid(profile, bid); + + break; + + case SMARTADSERVER: + handleSmartadserverBid(profile, bid); + + break; + case RUBICON: + handleRubiconBid(profile, site, bid); + + break; + default: + logMessage(`unsupported bidder '${bidder}', trying via bidder ortb2 fpd`); + const section = ((site) ? 'site' : 'user'); + const base = `ortb2.${section}.ext.data`; + + assignProfileToObject(bid, base, profile); + } +} + +/** + * set ortb2 global data + * @param {Object} profile + * @param {Boolean} site + * @returns {void} + */ +function setGlobalOrtb2(profile, site) { + const section = ((site) ? 'site' : 'user'); + const base = `${section}.ext.data`; + const addOrtb2 = {}; + + assignProfileToObject(addOrtb2, base, profile); + + if (!isEmpty(addOrtb2)) { + const testGlobal = getGlobal().getConfig('ortb2') || {}; + const ortb2 = { + ortb2: mergeDeep({}, testGlobal, addOrtb2) + }; + getGlobal().setConfig(ortb2); + } +} + +/** + * assign profile to object + * @param {Object} destination + * @param {string} base + * @param {Object} profile + * @returns {void} + */ +function assignProfileToObject(destination, base, profile) { + Object.keys(profile).forEach(key => { + const path = `${base}.${key}`; + deepSetValue(destination, path, profile[key]) + }) +} + +/** handle rubicon bid + * @param {Object} profile + * @param {Boolean} site + * @param {Object} bid + * @returns {void} + */ +function handleRubiconBid(profile, site, bid) { + const section = (site) ? 'inventory' : 'visitor'; + const base = `params.${section}`; + assignProfileToObject(bid, base, profile); +} + +/** handle appnexus/xandr bid + * @param {Object} profile + * @param {Object} bid + * @returns {void} + */ +function handleAppnexusBid(profile, bid) { + const base = 'params.keywords'; + assignProfileToObject(bid, base, profile); +} + +/** handle pubmatic bid + * @param {Object} profile + * @param {Object} bid + * @returns {void} + */ +function handlePubmaticBid(profile, bid) { + const sep = '|'; + const subsep = ','; + const bidKey = 'params.dctr'; + const target = []; + + const data = deepAccess(bid, bidKey); + if (data) { + data.split(sep).forEach(t => target.push(t)); + } + + Object.keys(profile).forEach(key => { + const value = profile[key].join(subsep); + const keyword = `${key}=${value}`; + if (target.indexOf(keyword) === -1) { + target.push(keyword); + } + }); + + deepSetValue(bid, bidKey, target.join(sep)); +} + +/** handle smartadserver bid + * @param {Object} profile + * @param {Object} bid + * @returns {void} + */ +function handleSmartadserverBid(profile, bid) { + const sep = ';'; + const bidKey = 'params.target'; + const target = []; + + const data = deepAccess(bid, bidKey); + if (data) { + data.split(sep).forEach(t => target.push(t)); + } + + Object.keys(profile).forEach(key => { + profile[key].forEach(value => { + const keyword = `${key}=${value}`; + if (target.indexOf(keyword) === -1) { + target.push(keyword); + } + }); + }); + deepSetValue(bid, bidKey, target.join(sep)); +} + +/** set bigsea contextual profile on module state + * @param {null|Object} data + * @returns {void} + */ +export function setWeboContextualProfile(data) { + if (data && Object.keys(data).length > 0) { + _weboContextualProfile = data; + } +} + +/** onSuccess callback type + * @callback successCallback + * @param {null|Object} data + * @returns {void} + */ + +/** onDone callback type + * @callback doneCallback + * @returns {void} + */ + +/** Fetch Bigsea Contextual Profile + * @param {WeboCtxConf} weboCtxConf + * @param {successCallback} onSuccess callback + * @param {doneCallback} onDone callback + * @returns {void} + */ +function fetchContextualProfile(weboCtxConf, onSuccess, onDone) { + const targetURL = weboCtxConf.targetURL || document.URL; + const token = weboCtxConf.token; + + let queryString = ''; + queryString = tryAppendQueryString(queryString, 'token', token); + queryString = tryAppendQueryString(queryString, 'url', targetURL); + + const url = `https://ctx.weborama.com/api/profile?${queryString}`; + + ajax(url, { + success: function(response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + onSuccess(data); + onDone(); + } catch (e) { + onDone(); + logError('unable to parse weborama data', e); + throw e; + } + } else if (req.status === 204) { + onDone(); + } + }, + error: function() { + onDone(); + logError('unable to get weborama data'); + } + }, + null, { + method: 'GET', + withCredentials: false, + }); +} + +export const weboramaSubmodule = { + name: SUBMODULE_NAME, + init: init, + getTargetingData: getTargetingData, + getBidRequestData: getBidRequestData, +}; + +submodule(MODULE_NAME, weboramaSubmodule); diff --git a/modules/weboramaRtdProvider.md b/modules/weboramaRtdProvider.md new file mode 100644 index 00000000000..732944c6e1c --- /dev/null +++ b/modules/weboramaRtdProvider.md @@ -0,0 +1,157 @@ +# Weborama Real-Time Data Submodule + +``` +Module Name: Weborama Rtd Provider +Module Type: Rtd Provider +Maintainer: prebid-support@weborama.com +``` + +# Description + +Weborama provides a Semantic AI Contextual API that classifies in Real-time a web page seen by a web user within generic and custom topics. It enables publishers to better monetize their inventory and unlock it to programmatic. + +Contact prebid-support@weborama.com for information. + +### Publisher Usage + +Compile the Weborama RTD module into your Prebid build: + +`gulp build --modules=rtdModule,weboramaRtdProvider` + +Add the Weborama RTD provider to your Prebid config. + +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function () { + pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + setPrebidTargeting: true, // optional + sendToBidders: true, // optional + onData: function(data, site){ // optional + var kind = (site)? 'site' : 'user'; + console.log('onData', kind, data); + }, + weboCtxConf: { + token: "to-be-defined", // mandatory + targetURL: "https://prebid.org", // default is document.URL + setPrebidTargeting: true, // override param.setPrebidTargeting or default true + sendToBidders: true, // override param.sendToBidders or default true + defaultProfile: { // optional + webo_ctx: ['moon'], + webo_ds: ['bar'] + } + //, onData: function (data, ...) { ...} + }, + weboUserDataConf: { + accountId: 12345, // optional, used for logging + setPrebidTargeting: true, // override param.setPrebidTargeting or default true + sendToBidders: true, // override param.sendToBidders or default true + defaultProfile: { // optional + webo_cs: ['Red'], + webo_audiences: ['bam'] + }, + localStorageProfileKey: 'webo_wam2gam_entry' // default + //, onData: function (data, ...) { ...} + } + } + }] + } + }); +}); +``` + +### Parameter Descriptions for the Weborama Configuration Section + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Mandatory. Always 'Weborama' | +| waitForIt | Boolean | Mandatory. Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false but recommended to true | +| params | Object | | Optional | +| params.setPrebidTargeting | Boolean | If true, may use the profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | +| params.sendToBidders | Boolean | If true, may send the profile to all bidders | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | +| params.weboCtxConf | Object | Weborama Contextual Configuration | Optional +| params.weboUserDataConf | Object | Weborama User-Centric Configuration | Optional | +| params.onData | Callback | If set, will receive the profile and site flag | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | + +#### Contextual Configuration + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| token | String | Security Token provided by Weborama, unique per client | Mandatory | +| targetURL | String | Url to be profiled in the contextual api | Optional. Defaults to `document.URL` | +| setPrebidTargeting|Boolean|If true, will use the contextual profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or **true**.| +| sendToBidders|Boolean|If true, will send the contextual profile to all bidders| Optional. Default is `params.sendToBidders` (if any) or **true**.| +| defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | +| onData | Callback | If set, will receive the profile and site flag | Optional. Default is `params.onData` (if any) or log via prebid debug | +| enabled | Boolean| if false, will ignore this configuration| default true| + +#### User-Centric Configuration + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| accountId|Number|WAM account id. If present, will be used on logging and statistics| Optional.| +| setPrebidTargeting|Boolean|If true, will use the user profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or **true**.| +| sendToBidders|Boolean|If true, will send the user profile to all bidders| Optional. Default is `params.sendToBidders` (if any) or **true**.| +| onData | Callback | If set, will receive the profile and site flag | Optional. Default is `params.onData` (if any) or log via prebid debug | +| defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | +| localStorageProfileKey| String | can be used to customize the local storage key | Optional | +| enabled | Boolean| if false, will ignore this configuration| default true| + +### Supported Bidders + +We currently support the following bidder adapters: +* SmartADServer SSP +* PubMatic SSP +* AppNexus SSP +* Rubicon SSP + +We also set the bidder and global ortb2 `site` and `user` sections. The following bidders may support it, to be sure, check the `First Party Data Support` on the feature list for the particular bidder from here: https://docs.prebid.org/dev-docs/bidders + +* Adagio +* AdformOpenRTB +* AdKernel +* AdMixer +* Adnuntius +* Adrelevantis +* adxcg +* AMX RTB +* Avocet +* BeOp +* Criteo +* Etarget +* Inmar +* Index Exchange +* Livewrapped +* Mediakeys +* NoBid +* OpenX +* Opt Out Advertising +* Ozone Project +* Proxistore +* Rise +* Smaato +* Sonobi +* TheMediaGrid +* TripleLift +* TrustX +* Yahoo SSP +* Yieldlab +* Zeta Global Ssp + +### Testing + +To view an example of available segments returned by Weborama's backends: + +`gulp serve --notest --nolint --modules=rtdModule,weboramaRtdProvider,smartadserverBidAdapter,pubmaticBidAdapter,appnexusBidAdapter,rubiconBidAdapter,criteoBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/weboramaRtdProvider_example.html` diff --git a/modules/welectBidAdapter.js b/modules/welectBidAdapter.js index 467b628c944..d88a3f4c3e2 100644 --- a/modules/welectBidAdapter.js +++ b/modules/welectBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'welect'; @@ -19,7 +19,7 @@ export const spec = { */ isBidRequestValid: function (bid) { return ( - utils.deepAccess(bid, 'mediaTypes.video.context') === 'instream' && + deepAccess(bid, 'mediaTypes.video.context') === 'instream' && !!bid.params.placementId ); }, @@ -32,7 +32,7 @@ export const spec = { buildRequests: function (validBidRequests) { return validBidRequests.map((bidRequest) => { let rawSizes = - utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') || + deepAccess(bidRequest, 'mediaTypes.video.playerSize') || bidRequest.sizes; let size = rawSizes[0]; @@ -84,7 +84,21 @@ export const spec = { } const bidResponses = []; - const bidResponse = responseBody.bidResponse; + const bidResponse = { + requestId: responseBody.bidResponse.requestId, + cpm: responseBody.bidResponse.cpm, + width: responseBody.bidResponse.width, + height: responseBody.bidResponse.height, + creativeId: responseBody.bidResponse.creativeId, + currency: responseBody.bidResponse.currency, + netRevenue: responseBody.bidResponse.netRevenue, + ttl: responseBody.bidResponse.ttl, + ad: responseBody.bidResponse.ad, + vastUrl: responseBody.bidResponse.vastUrl, + meta: { + advertiserDomains: responseBody.bidResponse.meta.advertiserDomains + } + }; bidResponses.push(bidResponse); return bidResponses; }, diff --git a/modules/widespaceBidAdapter.js b/modules/widespaceBidAdapter.js index 3cea5ca57a1..7890628f94b 100644 --- a/modules/widespaceBidAdapter.js +++ b/modules/widespaceBidAdapter.js @@ -61,7 +61,7 @@ export const spec = { 'gdprCmp': bidderRequest && bidderRequest.gdprConsent ? 1 : 0, 'hb': '1', 'hb.cd': CUST_DATA ? encodedParamValue(CUST_DATA) : '', - 'hb.floor': bid.bidfloor || '', + 'hb.floor': '', 'hb.spb': i === 0 ? pixelSyncPossibility() : -1, 'hb.ver': WS_ADAPTER_VERSION, 'hb.name': 'prebidjs-$prebid.version$', @@ -95,10 +95,9 @@ export const spec = { // Include debug data when available if (!isInHostileIframe) { - const DEBUG_AD = (find(window.top.location.hash.split('&'), + data.forceAdId = (find(window.top.location.hash.split('&'), val => includes(val, 'WS_DEBUG_FORCEADID') ) || '').split('=')[1]; - data.forceAdId = DEBUG_AD; } // GDPR Consent info @@ -151,7 +150,10 @@ export const spec = { netRevenue: Boolean(bid.netRev), ttl: bid.ttl, referrer: getTopWindowReferrer(), - ad: bid.code + ad: bid.code, + meta: { + advertiserDomains: bid.adomain || [] + } }); } }); @@ -223,7 +225,6 @@ function visibleOnLoad(element) { const topPos = element.getBoundingClientRect().top; return topPos < screen.height && topPos >= window.top.pageYOffset ? 1 : 0; } - ; return ''; } diff --git a/modules/widespaceBidAdapter.md b/modules/widespaceBidAdapter.md index 1ca2b61d406..fe87a620a86 100644 --- a/modules/widespaceBidAdapter.md +++ b/modules/widespaceBidAdapter.md @@ -1,8 +1,8 @@ # Overview -**Module Name:** Widespace Bidder Adapter. -**Module Type:** Bidder Adapter. +**Module Name:** Widespace Bidder Adapter.\ +**Module Type:** Bidder Adapter.\ **Maintainer:** support@widespace.com @@ -38,3 +38,4 @@ Banner and video formats are supported. } ]; ``` + diff --git a/modules/windtalkerBidAdapter.js b/modules/windtalkerBidAdapter.js deleted file mode 100644 index 512cc7f839b..00000000000 --- a/modules/windtalkerBidAdapter.js +++ /dev/null @@ -1,307 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import includes from 'core-js-pure/features/array/includes.js'; - -const NATIVE_DEFAULTS = { - TITLE_LEN: 100, - DESCR_LEN: 200, - SPONSORED_BY_LEN: 50, - IMG_MIN: 150, - ICON_MIN: 50, -}; -const DEFAULT_MIMES = ['video/mp4', 'video/webm', 'application/x-shockwave-flash', 'application/javascript']; -const VIDEO_TARGETING = ['mimes', 'skippable', 'playback_method', 'protocols', 'api']; -const DEFAULT_PROTOCOLS = [2, 3, 5, 6]; -const DEFAULT_APIS = [1, 2]; - -export const spec = { - - code: 'windtalker', - supportedMediaTypes: ['banner', 'native', 'video'], - - isBidRequestValid: bid => ( - !!(bid && bid.params && bid.params.pubId && bid.params.placementId) - ), - buildRequests: (bidRequests, bidderRequest) => { - const request = { - id: bidRequests[0].bidderRequestId, - at: 2, - imp: bidRequests.map(slot => impression(slot)), - site: site(bidRequests), - app: app(bidRequests), - device: device(bidRequests), - }; - applyGdpr(bidderRequest, request); - return { - method: 'POST', - url: 'https://windtalkerdisplay.hb.adp3.net/', - data: JSON.stringify(request), - }; - }, - interpretResponse: (response, request) => ( - bidResponseAvailable(request, response.body) - ), -}; - -function bidResponseAvailable(bidRequest, bidResponse) { - const idToImpMap = {}; - const idToBidMap = {}; - const ortbRequest = parse(bidRequest.data); - ortbRequest.imp.forEach(imp => { - idToImpMap[imp.id] = imp; - }); - if (bidResponse) { - bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => { - idToBidMap[bid.impid] = bid; - })); - } - const bids = []; - Object.keys(idToImpMap).forEach(id => { - if (idToBidMap[id]) { - const bid = {}; - bid.requestId = id; - bid.adId = id; - bid.creativeId = id; - bid.cpm = idToBidMap[id].price; - bid.currency = bidResponse.cur; - bid.ttl = 360; - bid.netRevenue = true; - if (idToImpMap[id]['native']) { - bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); - let nurl = idToBidMap[id].nurl; - nurl = nurl.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); - nurl = nurl.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); - nurl = nurl.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); - nurl = nurl.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); - bid['native']['impressionTrackers'] = [nurl]; - bid.mediaType = 'native'; - } else if (idToImpMap[id]['video']) { - bid.vastUrl = idToBidMap[id].adm; - bid.vastUrl = bid.vastUrl.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); - bid.crid = idToBidMap[id].crid; - bid.width = idToImpMap[id].video.w; - bid.height = idToImpMap[id].video.h; - bid.mediaType = 'video'; - } else if (idToImpMap[id]['banner']) { - bid.ad = idToBidMap[id].adm; - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_AD_ID(%7D|\})/gi, idToBidMap[id].adid); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); - bid.width = idToBidMap[id].w; - bid.height = idToBidMap[id].h; - bid.mediaType = 'banner'; - } - bids.push(bid); - } - }); - return bids; -} -function impression(slot) { - return { - id: slot.bidId, - secure: window.location.protocol === 'https:' ? 1 : 0, - 'banner': banner(slot), - 'native': nativeImpression(slot), - 'video': videoImpression(slot), - bidfloor: slot.params.bidFloor || '0.000001', - tagid: slot.params.placementId.toString(), - }; -} - -function banner(slot) { - if (slot.mediaType === 'banner' || utils.deepAccess(slot, 'mediaTypes.banner')) { - const sizes = utils.deepAccess(slot, 'mediaTypes.banner.sizes'); - if (sizes.length > 1) { - let format = []; - for (let f = 0; f < sizes.length; f++) { - format.push({'w': sizes[f][0], 'h': sizes[f][1]}); - } - return {'format': format}; - } else { - return { - w: sizes[0][0], - h: sizes[0][1] - } - } - } - return null; -} - -function videoImpression(slot) { - if (slot.mediaType === 'video' || utils.deepAccess(slot, 'mediaTypes.video')) { - const sizes = utils.deepAccess(slot, 'mediaTypes.video.playerSize'); - const video = { - w: sizes[0][0], - h: sizes[0][1], - mimes: DEFAULT_MIMES, - protocols: DEFAULT_PROTOCOLS, - api: DEFAULT_APIS, - }; - if (slot.params.video) { - Object.keys(slot.params.video).filter(param => includes(VIDEO_TARGETING, param)).forEach(param => video[param] = slot.params.video[param]); - } - return video; - } - return null; -} - -function nativeImpression(slot) { - if (slot.mediaType === 'native' || utils.deepAccess(slot, 'mediaTypes.native')) { - const assets = []; - addAsset(assets, titleAsset(1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN)); - addAsset(assets, dataAsset(2, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN)); - addAsset(assets, dataAsset(3, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN)); - addAsset(assets, imageAsset(4, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN)); - addAsset(assets, imageAsset(5, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN)); - return { - request: JSON.stringify({ assets }), - ver: '1.1', - }; - } - return null; -} - -function addAsset(assets, asset) { - if (asset) { - assets.push(asset); - } -} - -function titleAsset(id, params, defaultLen) { - if (params) { - return { - id, - required: params.required ? 1 : 0, - title: { - len: params.len || defaultLen, - }, - }; - } - return null; -} - -function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { - return params ? { - id, - required: params.required ? 1 : 0, - img: { - type, - wmin: params.wmin || defaultMinWidth, - hmin: params.hmin || defaultMinHeight, - } - } : null; -} - -function dataAsset(id, params, type, defaultLen) { - return params ? { - id, - required: params.required ? 1 : 0, - data: { - type, - len: params.len || defaultLen, - } - } : null; -} - -function site(bidderRequest) { - const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; - const siteId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.siteId : '0'; - const appParams = bidderRequest[0].params.app; - if (!appParams) { - return { - publisher: { - id: pubId.toString(), - domain: window.location.hostname, - }, - id: siteId.toString(), - ref: window.top.document.referrer, - page: window.location.href, - } - } - return null; -} - -function app(bidderRequest) { - const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; - const appParams = bidderRequest[0].params.app; - if (appParams) { - return { - publisher: { - id: pubId.toString(), - }, - id: appParams.id, - name: appParams.name, - bundle: appParams.bundle, - storeurl: appParams.storeUrl, - domain: appParams.domain, - } - } - return null; -} - -function device(bidderRequest) { - const lat = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.latitude : ''; - const lon = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.longitude : ''; - const ifa = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.ifa : ''; - return { - dnt: utils.getDNT() ? 1 : 0, - ua: navigator.userAgent, - language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), - w: (window.screen.width || window.innerWidth), - h: (window.screen.height || window.innerHeigh), - geo: { - lat: lat, - lon: lon, - }, - ifa: ifa, - }; -} - -function parse(rawResponse) { - try { - if (rawResponse) { - return JSON.parse(rawResponse); - } - } catch (ex) { - utils.logError('windtalker.parse', 'ERROR', ex); - } - return null; -} - -function applyGdpr(bidderRequest, ortbRequest) { - if (bidderRequest && bidderRequest.gdprConsent) { - ortbRequest.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; - ortbRequest.user = { ext: { consent: bidderRequest.gdprConsent.consentString } }; - } -} - -function nativeResponse(imp, bid) { - if (imp['native']) { - const nativeAd = parse(bid.adm); - const keys = {}; - keys.image = {}; - keys.icon = {}; - if (nativeAd && nativeAd['native'] && nativeAd['native'].assets) { - nativeAd['native'].assets.forEach(asset => { - keys.title = asset.title ? asset.title.text : keys.title; - keys.body = asset.data && asset.id === 2 ? asset.data.value : keys.body; - keys.sponsoredBy = asset.data && asset.id === 3 ? asset.data.value : keys.sponsoredBy; - keys.icon.url = asset.img && asset.id === 4 ? asset.img.url : keys.icon.url; - keys.icon.width = asset.img && asset.id === 4 ? asset.img.w : keys.icon.width; - keys.icon.height = asset.img && asset.id === 4 ? asset.img.h : keys.icon.height; - keys.image.url = asset.img && asset.id === 5 ? asset.img.url : keys.image.url; - keys.image.width = asset.img && asset.id === 5 ? asset.img.w : keys.image.width; - keys.image.height = asset.img && asset.id === 5 ? asset.img.h : keys.image.height; - }); - if (nativeAd['native'].link) { - keys.clickUrl = encodeURIComponent(nativeAd['native'].link.url); - } - return keys; - } - } - return null; -} - -registerBidder(spec); diff --git a/modules/winrBidAdapter.js b/modules/winrBidAdapter.js new file mode 100644 index 00000000000..9213c113460 --- /dev/null +++ b/modules/winrBidAdapter.js @@ -0,0 +1,614 @@ +import { convertCamelToUnderscore, isArray, isNumber, isPlainObject, deepAccess, logError, convertTypes, getParameterByName, getBidRequest, isEmpty, transformBidderParamKeywords, isFn } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { 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 { getStorageManager } from '../src/storageManager.js'; + +export const storage = getStorageManager(); + +const BIDDER_CODE = 'winr'; +const URL = 'https://ib.adnxs.com/ut/v3/prebid'; +const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; +const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; +const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately +const SOURCE = 'pbjs'; +const DEFAULT_CURRENCY = 'USD'; +const GATE_COOKIE_NAME = 'wnr_gate'; + +function buildBid(bidData) { + const bid = bidData; + const position = { + domParent: bid.meta.domParent ? `'${bid.meta.domParent}'` : null, + child: bid.meta.child ? bid.meta.child : 4 + } + bid.ad = wrapAd(bid, position); + return bid; +} + +function getMediaTypeFromBid(bid) { + return bid.mediaTypes && Object.keys(bid.mediaTypes)[0]; +} + +function wrapAd(bid, position) { + return ` + + + + + + + + + + `; +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['wnr'], + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + // Return false for each bid request if the media type is not 'banner' + if (getMediaTypeFromBid(bid) !== BANNER) { + return false; + } + + // Return false for each bid request if the cookies disabled + if (!storage.cookiesAreEnabled()) { + return false; + } + + // Return false for each bid request if the gate cookie is set + if (storage.getCookie(GATE_COOKIE_NAME) !== null) { + return false; + } + + // Return false for each bid request if no placementId exists + if (!bid.params.placementId) { + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests, bidderRequest) { + const tags = bidRequests.map(bidToTag); + const userObjBid = find(bidRequests, hasUserInfo); + let userObj = {}; + if (config.getConfig('coppa') === true) { + userObj = { 'coppa': true }; + } + + if (userObjBid) { + Object.keys(userObjBid.params.user) + .filter((param) => includes(USER_PARAMS, param)) + .forEach((param) => { + let uparam = convertCamelToUnderscore(param); + if ( + param === 'segments' && + isArray(userObjBid.params.user[param]) + ) { + let segs = []; + userObjBid.params.user[param].forEach((val) => { + if (isNumber(val)) { + segs.push({ id: val }); + } else if (isPlainObject(val)) { + segs.push(val); + } + }); + userObj[uparam] = segs; + } else if (param !== 'segments') { + userObj[uparam] = userObjBid.params.user[param]; + } + }); + } + + const appDeviceObjBid = find(bidRequests, hasAppDeviceInfo); + let appDeviceObj; + if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app) { + appDeviceObj = {}; + Object.keys(appDeviceObjBid.params.app) + .filter(param => includes(APP_DEVICE_PARAMS, param)) + .forEach(param => appDeviceObj[param] = appDeviceObjBid.params.app[param]); + } + + const appIdObjBid = find(bidRequests, hasAppId); + let appIdObj; + if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { + appIdObj = { + appid: appIdObjBid.params.app.id + }; + } + + const memberIdBid = find(bidRequests, hasMemberId); + const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; + const schain = bidRequests[0].schain; + + const payload = { + tags: [...tags], + user: userObj, + sdk: { + source: SOURCE, + version: '$prebid.version$', + }, + schain: schain + }; + + if (member > 0) { + payload.member_id = member; + } + + if (appDeviceObjBid) { + payload.device = appDeviceObj + } + if (appIdObjBid) { + payload.app = appIdObj; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + // note - objects for impbus use underscore instead of camelCase + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies, + }; + } + + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + + if (bidderRequest && bidderRequest.refererInfo) { + let refererinfo = { + rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + rd_stk: bidderRequest.refererInfo.stack + .map((url) => encodeURIComponent(url)) + .join(','), + }; + payload.referrer_detection = refererinfo; + } + + if (bidRequests[0].userId) { + let eids = []; + + addUserId(eids, deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null); + addUserId(eids, deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); + addUserId(eids, deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); + addUserId(eids, deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); + addUserId(eids, deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID'); + addUserId(eids, deepAccess(bidRequests[0], `userId.uid2.id`), 'uidapi.com', 'UID2'); + + if (eids.length) { + payload.eids = eids; + } + } + + if (tags[0].publisher_id) { + payload.publisher_id = tags[0].publisher_id; + } + + const request = formatRequest(payload, bidderRequest); + return request; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, { bidderRequest }) { + serverResponse = serverResponse.body; + const bids = []; + if (!serverResponse || serverResponse.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; + if (serverResponse && serverResponse.error) { + errorMessage += `: ${serverResponse.error}`; + } + logError(errorMessage); + return bids; + } + + if (serverResponse.tags) { + serverResponse.tags.forEach((serverBid) => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid) { + if ( + rtbBid.cpm !== 0 && + includes(this.supportedMediaTypes, rtbBid.ad_type) + ) { + const bid = newBid(serverBid, rtbBid, bidderRequest); + bid.mediaType = parseMediaType(rtbBid); + bids.push(bid); + } + } + }); + } + + return bids.map(bid => buildBid(bid)); + }, + + getUserSyncs: function (syncOptions) { + if (syncOptions.iframeEnabled) { + return [ + { + type: 'iframe', + url: 'https://acdn.adnxs.com/dmp/async_usersync.html', + }, + ]; + } + }, + + transformBidParams: function (params, isOpenRtb) { + params = convertTypes( + { + member: 'string', + invCode: 'string', + placementId: 'number', + keywords: transformBidderParamKeywords, + publisherId: 'number', + }, + params + ); + + if (isOpenRtb) { + params.use_pmt_rule = + typeof params.usePaymentRule === 'boolean' + ? params.usePaymentRule + : false; + if (params.usePaymentRule) { + delete params.usePaymentRule; + } + + if (isPopulatedArray(params.keywords)) { + params.keywords.forEach(deleteValues); + } + + Object.keys(params).forEach((paramKey) => { + let convertedKey = convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + params[convertedKey] = params[paramKey]; + delete params[paramKey]; + } + }); + } + + return params; + }, +}; + +function isPopulatedArray(arr) { + return !!(isArray(arr) && arr.length > 0); +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + +function hasPurpose1Consent(bidderRequest) { + let result = true; + if (bidderRequest && bidderRequest.gdprConsent) { + if ( + bidderRequest.gdprConsent.gdprApplies && + bidderRequest.gdprConsent.apiVersion === 2 + ) { + result = !!( + deepAccess( + bidderRequest.gdprConsent, + 'vendorData.purpose.consents.1' + ) === true + ); + } + } + return result; +} + +function formatRequest(payload, bidderRequest) { + let request = []; + let options = { + withCredentials: true + }; + + let endpointUrl = URL; + + if (!hasPurpose1Consent(bidderRequest)) { + endpointUrl = URL_SIMPLE; + } + + if ( + getParameterByName('apn_test').toUpperCase() === 'TRUE' || + config.getConfig('apn_test') === true + ) { + options.customHeaders = { + 'X-Is-Test': 1, + }; + } + + const payloadString = JSON.stringify(payload); + request = { + method: 'POST', + url: endpointUrl, + data: payloadString, + bidderRequest, + options, + }; + + return request; +} + +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @param rtbBid + * @param bidderRequest + * @return Bid + */ +function newBid(serverBid, rtbBid, bidderRequest) { + const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); + const bid = { + adType: rtbBid.ad_type, + requestId: serverBid.uuid, + auctionId: bidRequest.auctionId, + cpm: rtbBid.cpm, + creativeId: rtbBid.creative_id, + brandCategoryId: rtbBid.brandCategoryId, + dealId: rtbBid.deal_id, + currency: DEFAULT_CURRENCY, + netRevenue: true, + ttl: 300, + source: rtbBid.content_source, + mediaSubtypeId: rtbBid.media_subtype_id, + mediaTypeId: rtbBid.media_type_id, + adUnitCode: bidRequest.adUnitCode, + buyerMemberId: rtbBid.buyer_member_id, + appnexus: { + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code, + } + }; + + // WE DON'T FULLY SUPPORT THIS ATM - future spot for adomain code; creating a stub for 5.0 compliance + if (rtbBid.adomain) { + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [] }); + } + + if (rtbBid.advertiser_id) { + bid.meta = Object.assign({}, bid.meta, { + advertiserId: rtbBid.advertiser_id, + }); + } + + if (bidRequest.params) { + const { placementId, siteId, domParent, child } = bidRequest.params; + bid.meta = Object.assign({}, bid.meta, { + placementId: placementId, + siteId: siteId, + domParent: domParent, + child: child + }); + } + + Object.assign(bid, { + width: rtbBid.rtb.banner.width, + height: rtbBid.rtb.banner.height, + }); + + try { + if (rtbBid.rtb.banner && rtbBid.rtb.trackers) { + bid.banner = Object.assign({}, bid.banner, { + content: rtbBid.rtb.banner.content, + width: rtbBid.rtb.banner.width, + height: rtbBid.rtb.banner.height, + trackers: rtbBid.rtb.trackers, + }); + } + } catch (error) { + logError('Error assigning ad', error); + } + return bid; +} + +function bidToTag(bid) { + const tag = {}; + tag.sizes = transformSizes(bid.sizes); + tag.primary_size = tag.sizes[0]; + tag.ad_types = []; + tag.uuid = bid.bidId; + if (bid.params.placementId) { + tag.id = parseInt(bid.params.placementId, 10); + } else { + tag.code = bid.params.invCode; + } + tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; + tag.use_pmt_rule = bid.params.usePaymentRule || false; + tag.prebid = true; + tag.disable_psa = true; + let bidFloor = getBidFloor(bid); + if (bidFloor) { + tag.reserve = bidFloor; + } + if (bid.params.trafficSourceCode) { + tag.traffic_source_code = bid.params.trafficSourceCode; + } + if (bid.params.privateSizes) { + tag.private_sizes = transformSizes(bid.params.privateSizes); + } + if (bid.params.pubClick) { + tag.pubclick = bid.params.pubClick; + } + if (bid.params.publisherId) { + tag.publisher_id = parseInt(bid.params.publisherId, 10); + } + if (bid.params.externalImpId) { + tag.external_imp_id = bid.params.externalImpId; + } + if (!isEmpty(bid.params.keywords)) { + let keywords = transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + tag.keywords = keywords; + } + + let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + tag.gpid = gpid; + } + + tag.hb_source = 1; + + if (tag.ad_types.length === 0) { + delete tag.ad_types; + } + + return tag; +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + let sizes = []; + let sizeObj = {}; + + if ( + isArray(requestSizes) && + requestSizes.length === 2 && + !isArray(requestSizes[0]) + ) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + sizes.push(sizeObj); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + sizes.push(sizeObj); + } + } + + return sizes; +} + +function hasUserInfo(bid) { + return !!bid.params.user; +} + +function hasMemberId(bid) { + return !!parseInt(bid.params.member, 10); +} + +function hasAppDeviceInfo(bid) { + if (bid.params) { + return !!bid.params.app + } +} + +function hasAppId(bid) { + if (bid.params && bid.params.app) { + return !!bid.params.app.id + } + return !!bid.params.app +} + +function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && find(tag.ads, (ad) => ad.rtb); +} + +function parseMediaType(rtbBid) { + const adType = rtbBid.ad_type; + if (adType !== BANNER) { + return false; + } + return BANNER; +} + +function addUserId(eids, id, source, rti) { + if (id) { + if (rti) { + eids.push({ source, id, rti_partner: rti }); + } else { + eids.push({ source, id }); + } + } + return eids; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.reserve) ? bid.params.reserve : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + +registerBidder(spec); diff --git a/modules/winrBidAdapter.md b/modules/winrBidAdapter.md new file mode 100644 index 00000000000..f9a73a6c0fa --- /dev/null +++ b/modules/winrBidAdapter.md @@ -0,0 +1,546 @@ +# Overview + +``` +Module Name: WINR Corporation Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech@winr.com.au +``` + +# Description + +WINR AdGate Bid Adaptor for Prebid.js. + +Connects to AppNexus exchange for bids. + +This bid adapter supports the Banner media type only. + +Please reach out to the WINR team before using this plugin to get `placementId`. + +`domParent` and `child` position settings are usually determined and remotely controlled for each publisher site by the WINR team. If you would prefer to have control over these settings, please get in touch. + +The code below returns a demo ad. + +# Test Parameters + +```js +var adUnits = [ + // Banner adUnit + { + code: "ad-unit", + mediaTypes: { + banner: { + sizes: [[1, 1]], + }, + }, + bids: [ + { + bidder: "winr", + params: { + placementId: 21764100, + domParent: ".blog-post", // optional + child: 4, // optional + }, + }, + ], + }, +]; +``` + +# Example page + +```html + + + + + + Prebid.js Banner Example + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ +
+
+
+

Title of a featured blog post

+

+ Multiple lines of text that form the lede, informing new readers + quickly and efficiently about what’s most interesting in this post’s + contents. +

+

+ Continue reading... +

+
+
+ +
+
+

From the Firehose

+ + +
+

Sample blog post

+ + +

+ This blog post shows a few different types of content that’s + supported and styled with Bootstrap. Basic typography, images, and + code are all supported. +

+
+

+ Yeah, she dances to her own beat. Oh, no. You could've been the + greatest. 'Cause, baby, you're a firework. Maybe a + reason why all the doors are closed. Open up your heart and just + let it begin. So très chic, yeah, she's a classic. +

+
+

+ Bikinis, zucchinis, Martinis, no weenies. I know there will be + sacrifice but that's the price. + This is how we do it. I'm not sticking around + to watch you go down. You think you're so rock and roll, but + you're really just a joke. I know one spark will shock the + world, yeah yeah. Can't replace you with a million rings. +

+
+

+ Trying to connect the dots, don't know what to tell my boss. + Before you met me I was alright but things were kinda heavy. You + just gotta ignite the light and let it shine. Glitter all over the + room pink flamingos in the pool. +

+

Sub-heading

+

+ You got the finest architecture. Passport stamps, she's + cosmopolitan. Fine, fresh, fierce, we got it on lock. Never + planned that one day I'd be losing you. She eats your heart out. +

+
    +
  • Got a motel and built a fort out of sheets.
  • +
  • Your kiss is cosmic, every move is magic.
  • +
  • Suiting up for my crowning battle.
  • +
+

+ Takes you miles high, so high, 'cause she’s got that one + international smile. +

+
    +
  1. Scared to rock the boat and make a mess.
  2. +
  3. I could have rewrite your addiction.
  4. +
  5. I know you get me so I let my walls come down.
  6. +
+

After a hurricane comes a rainbow.

+ + +
+ +
+
+ +
+ +
+
+

About

+

+ Saw you downtown singing the Blues. Watch you circle the drain. + Why don't you let me stop by? Heavy is the head that + wears the crown. Yes, we make angels cry, raining down on + earth from up above. +

+
+ + +
+
+ +
+ + + + + +``` + +# Example page with GPT + +```html + + + + + + Prebid.js Banner Example + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ +
+
+
+

Title of a featured blog post

+

+ Multiple lines of text that form the lede, informing new readers + quickly and efficiently about what’s most interesting in this post’s + contents. +

+

+ Continue reading... +

+
+
+ +
+
+

From the Firehose

+ + +
+

Sample blog post

+ + +

+ This blog post shows a few different types of content that’s + supported and styled with Bootstrap. Basic typography, images, and + code are all supported. +

+
+

+ Yeah, she dances to her own beat. Oh, no. You could've been the + greatest. 'Cause, baby, you're a firework. Maybe a + reason why all the doors are closed. Open up your heart and just + let it begin. So très chic, yeah, she's a classic. +

+
+

+ Bikinis, zucchinis, Martinis, no weenies. I know there will be + sacrifice but that's the price. + This is how we do it. I'm not sticking around + to watch you go down. You think you're so rock and roll, but + you're really just a joke. I know one spark will shock the + world, yeah yeah. Can't replace you with a million rings. +

+
+

+ Trying to connect the dots, don't know what to tell my boss. + Before you met me I was alright but things were kinda heavy. You + just gotta ignite the light and let it shine. Glitter all over the + room pink flamingos in the pool. +

+

Sub-heading

+

+ You got the finest architecture. Passport stamps, she's + cosmopolitan. Fine, fresh, fierce, we got it on lock. Never + planned that one day I'd be losing you. She eats your heart out. +

+
    +
  • Got a motel and built a fort out of sheets.
  • +
  • Your kiss is cosmic, every move is magic.
  • +
  • Suiting up for my crowning battle.
  • +
+

+ Takes you miles high, so high, 'cause she’s got that one + international smile. +

+
    +
  1. Scared to rock the boat and make a mess.
  2. +
  3. I could have rewrite your addiction.
  4. +
  5. I know you get me so I let my walls come down.
  6. +
+

After a hurricane comes a rainbow.

+ + +
+ +
+
+ +
+ +
+
+

About

+

+ Saw you downtown singing the Blues. Watch you circle the drain. + Why don't you let me stop by? Heavy is the head that + wears the crown. Yes, we make angels cry, raining down on + earth from up above. +

+
+ + +
+
+ +
+ + + + + +``` diff --git a/modules/wipesBidAdapter.js b/modules/wipesBidAdapter.js index f381bcb68a0..3d040fee8d3 100644 --- a/modules/wipesBidAdapter.js +++ b/modules/wipesBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { logWarn } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -13,7 +13,7 @@ function isBidRequestValid(bid) { case !!(bid.params.asid): break; default: - utils.logWarn(`isBidRequestValid Error. ${bid.params}, please check your implementation.`); + logWarn(`isBidRequestValid Error. ${bid.params}, please check your implementation.`); return false; } return true; @@ -54,6 +54,9 @@ function interpretResponse(serverResponse, bidRequest) { referrer: bidRequest.data.r || '', mediaType: BANNER, ad: response.ad_tag, + meta: { + advertiserDomains: response.advertiser_domain ? [response.advertiser_domain] : [] + } }; bidResponses.push(bidResponse); } diff --git a/modules/xhbBidAdapter.js b/modules/xhbBidAdapter.js deleted file mode 100644 index 9363eb97ddc..00000000000 --- a/modules/xhbBidAdapter.js +++ /dev/null @@ -1,457 +0,0 @@ -import { Renderer } from '../src/Renderer.js'; -import * as utils from '../src/utils.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'; - -const BIDDER_CODE = 'xhb'; -const URL = 'https://ib.adnxs.com/ut/v3/prebid'; -const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', - 'startdelay', 'skippable', 'playback_method', 'frameworks']; -const USER_PARAMS = ['age', 'external_uid', 'segments', 'gender', 'dnt', 'language']; -const NATIVE_MAPPING = { - body: 'description', - cta: 'ctatext', - image: { - serverName: 'main_image', - requiredParams: { required: true }, - minimumParams: { sizes: [{}] }, - }, - icon: { - serverName: 'icon', - requiredParams: { required: true }, - minimumParams: { sizes: [{}] }, - }, - sponsoredBy: 'sponsored_by', -}; -const SOURCE = 'pbjs'; - -export const spec = { - code: BIDDER_CODE, - aliases: [], - supportedMediaTypes: [BANNER, VIDEO, NATIVE], - - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - return !!(bid.params.placementId || (bid.params.member && bid.params.invCode)); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(bidRequests, bidderRequest) { - const tags = bidRequests.map(bidToTag); - const userObjBid = find(bidRequests, hasUserInfo); - let userObj; - if (userObjBid) { - userObj = {}; - Object.keys(userObjBid.params.user) - .filter(param => includes(USER_PARAMS, param)) - .forEach(param => userObj[param] = userObjBid.params.user[param]); - } - - const memberIdBid = find(bidRequests, hasMemberId); - const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; - - const payload = { - tags: [...tags], - user: userObj, - sdk: { - source: SOURCE, - version: '$prebid.version$' - } - }; - if (member > 0) { - payload.member_id = member; - } - - if (bidderRequest && bidderRequest.gdprConsent) { - // note - objects for impbus use underscore instead of camelCase - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - } - - const payloadString = JSON.stringify(payload); - return { - method: 'POST', - url: URL, - data: payloadString, - bidderRequest - }; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, {bidderRequest}) { - serverResponse = serverResponse.body; - const bids = []; - if (!serverResponse || serverResponse.error) { - let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; - if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } - utils.logError(errorMessage); - return bids; - } - - if (serverResponse.tags) { - serverResponse.tags.forEach(serverBid => { - const rtbBid = getRtbBid(serverBid); - if (rtbBid) { - if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { - const bid = newBid(serverBid, rtbBid, bidderRequest); - bid.mediaType = parseMediaType(rtbBid); - bids.push(bid); - } - } - }); - } - return bids; - }, - - getUserSyncs: function(syncOptions) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: 'https://acdn.adnxs.com/dmp/async_usersync.html' - }]; - } - } -}; - -function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { - const renderer = Renderer.install({ - id: rtbBid.renderer_id, - url: rtbBid.renderer_url, - config: rendererOptions, - loaded: false, - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); - } - - renderer.setEventHandlers({ - impression: () => utils.logMessage('xhb outstream video impression event'), - loaded: () => utils.logMessage('xhb outstream video loaded event'), - ended: () => { - utils.logMessage('xhb outstream renderer video event'); - document.querySelector(`#${adUnitCode}`).style.display = 'none'; - } - }); - return renderer; -} - -/* Turn keywords parameter into ut-compatible format */ -function getKeywords(keywords) { - let arrs = []; - - utils._each(keywords, (v, k) => { - if (utils.isArray(v)) { - let values = []; - utils._each(v, (val) => { - val = utils.getValueString('keywords.' + k, val); - if (val) { values.push(val); } - }); - v = values; - } else { - v = utils.getValueString('keywords.' + k, v); - if (utils.isStr(v)) { - v = [v]; - } else { - return; - } // unsuported types - don't send a key - } - arrs.push({key: k, value: v}); - }); - - return arrs; -} - -/** - * Unpack the Server's Bid into a Prebid-compatible one. - * @param serverBid - * @param rtbBid - * @param bidderRequest - * @return Bid - */ -function newBid(serverBid, rtbBid, bidderRequest) { - const bid = { - requestId: serverBid.uuid, - cpm: 0.00, - creativeId: rtbBid.creative_id, - dealId: 99999999, - currency: 'USD', - netRevenue: true, - ttl: 300, - appnexus: { - buyerMemberId: rtbBid.buyer_member_id - } - }; - - if (rtbBid.rtb.video) { - Object.assign(bid, { - width: rtbBid.rtb.video.player_width, - height: rtbBid.rtb.video.player_height, - vastUrl: rtbBid.rtb.video.asset_url, - vastImpUrl: rtbBid.notify_url, - ttl: 3600 - }); - // This supports Outstream Video - if (rtbBid.renderer_url) { - const rendererOptions = utils.deepAccess( - bidderRequest.bids[0], - 'renderer.options' - ); - - Object.assign(bid, { - adResponse: serverBid, - renderer: newRenderer(bid.adUnitCode, rtbBid, rendererOptions) - }); - bid.adResponse.ad = bid.adResponse.ads[0]; - bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; - } - } else if (rtbBid.rtb[NATIVE]) { - const nativeAd = rtbBid.rtb[NATIVE]; - bid[NATIVE] = { - title: nativeAd.title, - body: nativeAd.desc, - cta: nativeAd.ctatext, - sponsoredBy: nativeAd.sponsored, - clickUrl: nativeAd.link.url, - clickTrackers: nativeAd.link.click_trackers, - impressionTrackers: nativeAd.impression_trackers, - javascriptTrackers: nativeAd.javascript_trackers, - }; - if (nativeAd.main_img) { - bid['native'].image = { - url: nativeAd.main_img.url, - height: nativeAd.main_img.height, - width: nativeAd.main_img.width, - }; - } - if (nativeAd.icon) { - bid['native'].icon = { - url: nativeAd.icon.url, - height: nativeAd.icon.height, - width: nativeAd.icon.width, - }; - } - } else { - Object.assign(bid, { - width: rtbBid.rtb.banner.width, - height: rtbBid.rtb.banner.height, - ad: rtbBid.rtb.banner.content - }); - try { - const url = rtbBid.rtb.trackers[0].impression_urls[0]; - const tracker = utils.createTrackPixelHtml(url); - bid.ad += tracker; - } catch (error) { - utils.logError('Error appending tracking pixel', error); - } - } - - return bid; -} - -function bidToTag(bid) { - const tag = {}; - tag.sizes = transformSizes(bid.sizes); - tag.primary_size = tag.sizes[0]; - tag.ad_types = []; - tag.uuid = bid.bidId; - if (bid.params.placementId) { - tag.id = parseInt(bid.params.placementId, 10); - } else { - tag.code = bid.params.invCode; - } - tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false; - tag.prebid = true; - tag.disable_psa = true; - if (bid.params.reserve) { - tag.reserve = bid.params.reserve; - } - if (bid.params.position) { - tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; - } - if (bid.params.trafficSourceCode) { - tag.traffic_source_code = bid.params.trafficSourceCode; - } - if (bid.params.privateSizes) { - tag.private_sizes = transformSizes(bid.params.privateSizes); - } - if (bid.params.supplyType) { - tag.supply_type = bid.params.supplyType; - } - if (bid.params.pubClick) { - tag.pubclick = bid.params.pubClick; - } - if (bid.params.extInvCode) { - tag.ext_inv_code = bid.params.extInvCode; - } - if (bid.params.externalImpId) { - tag.external_imp_id = bid.params.externalImpId; - } - if (!utils.isEmpty(bid.params.keywords)) { - tag.keywords = getKeywords(bid.params.keywords); - } - - if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) { - tag.ad_types.push(NATIVE); - - if (bid.nativeParams) { - const nativeRequest = buildNativeRequest(bid.nativeParams); - tag[NATIVE] = {layouts: [nativeRequest]}; - } - } - - const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - - if (bid.mediaType === VIDEO || videoMediaType) { - tag.ad_types.push(VIDEO); - } - - // instream gets vastUrl, outstream gets vastXml - if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { - tag.require_asset_url = true; - } - - if (bid.params.video) { - tag.video = {}; - // place any valid video params on the tag - Object.keys(bid.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => tag.video[param] = bid.params.video[param]); - } - - if ( - (utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) || - (bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER])) - ) { - tag.ad_types.push(BANNER); - } - - return tag; -} - -/* Turn bid request sizes into ut-compatible format */ -function transformSizes(requestSizes) { - let sizes = []; - let sizeObj = {}; - - if (utils.isArray(requestSizes) && requestSizes.length === 2 && - !utils.isArray(requestSizes[0])) { - sizeObj.width = parseInt(requestSizes[0], 10); - sizeObj.height = parseInt(requestSizes[1], 10); - sizes.push(sizeObj); - } else if (typeof requestSizes === 'object') { - for (let i = 0; i < requestSizes.length; i++) { - let size = requestSizes[i]; - sizeObj = {}; - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - sizes.push(sizeObj); - } - } - - return sizes; -} - -function hasUserInfo(bid) { - return !!bid.params.user; -} - -function hasMemberId(bid) { - return !!parseInt(bid.params.member, 10); -} - -function getRtbBid(tag) { - return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); -} - -function buildNativeRequest(params) { - const request = {}; - - // map standard prebid native asset identifier to /ut parameters - // e.g., tag specifies `body` but /ut only knows `description`. - // mapping may be in form {tag: ''} or - // {tag: {serverName: '', requiredParams: {...}}} - Object.keys(params).forEach(key => { - // check if one of the forms is used, otherwise - // a mapping wasn't specified so pass the key straight through - const requestKey = - (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) || - NATIVE_MAPPING[key] || - key; - - // required params are always passed on request - const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; - request[requestKey] = Object.assign({}, requiredParams, params[key]); - - // minimum params are passed if no non-required params given on adunit - const minimumParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].minimumParams; - - if (requiredParams && minimumParams) { - // subtract required keys from adunit keys - const adunitKeys = Object.keys(params[key]); - const requiredKeys = Object.keys(requiredParams); - const remaining = adunitKeys.filter(key => !includes(requiredKeys, key)); - - // if none are left over, the minimum params needs to be sent - if (remaining.length === 0) { - request[requestKey] = Object.assign({}, request[requestKey], minimumParams); - } - } - }); - - return request; -} - -function outstreamRender(bid) { - // push to render queue because ANOutstreamVideo may not be loaded yet - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - tagId: bid.adResponse.tag_id, - sizes: [bid.getSize().split('x')], - targetId: bid.adUnitCode, // target div id to render video - uuid: bid.adResponse.uuid, - adResponse: bid.adResponse, - rendererOptions: bid.renderer.getConfig() - }, handleOutstreamRendererEvents.bind(null, bid)); - }); -} - -function handleOutstreamRendererEvents(bid, id, eventName) { - bid.renderer.handleVideoEvent({ id, eventName }); -} - -function parseMediaType(rtbBid) { - const adType = rtbBid.ad_type; - if (adType === VIDEO) { - return VIDEO; - } else if (adType === NATIVE) { - return NATIVE; - } else { - return BANNER; - } -} - -registerBidder(spec); diff --git a/modules/yahoosspBidAdapter.js b/modules/yahoosspBidAdapter.js new file mode 100644 index 00000000000..671a2a86d14 --- /dev/null +++ b/modules/yahoosspBidAdapter.js @@ -0,0 +1,643 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { deepAccess, isFn, isStr, isNumber, isArray, isEmpty, isPlainObject, generateUUID, logInfo, logWarn } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { Renderer } from '../src/Renderer.js'; + +const INTEGRATION_METHOD = 'prebid.js'; +const BIDDER_CODE = 'yahoossp'; +const GVLID = 25; +const ADAPTER_VERSION = '1.0.2'; +const PREBID_VERSION = '$prebid.version$'; +const DEFAULT_BID_TTL = 300; +const TEST_MODE_DCN = '8a969516017a7a396ec539d97f540011'; +const TEST_MODE_PUBID_DCN = '1234567'; +const TEST_MODE_BANNER_POS = '8a969978017a7aaabab4ab0bc01a0009'; +const TEST_MODE_VIDEO_POS = '8a96958a017a7a57ac375d50c0c700cc'; +const DEFAULT_RENDERER_TIMEOUT = 700; +const DEFAULT_CURRENCY = 'USD'; +const SSP_ENDPOINT_DCN_POS = 'https://c2shb.pubgw.yahoo.com/bidRequest'; +const SSP_ENDPOINT_PUBID = 'https://c2shb.pubgw.yahoo.com/admax/bid/partners/PBJS'; +const SUPPORTED_USER_ID_SOURCES = [ + 'admixer.net', + 'adserver.org', + 'adtelligent.com', + 'akamai.com', + 'amxrtb.com', + 'audigent.com', + 'britepool.com', + 'criteo.com', + 'crwdcntrl.net', + 'deepintent.com', + 'hcn.health', + 'id5-sync.com', + 'idx.lat', + 'intentiq.com', + 'intimatemerger.com', + 'liveintent.com', + 'liveramp.com', + 'mediawallahscript.com', + 'merkleinc.com', + 'netid.de', + 'neustar.biz', + 'nextroll.com', + 'novatiq.com', + 'parrable.com', + 'pubcid.org', + 'quantcast.com', + 'quantcast.com', + 'tapad.com', + 'uidapi.com', + 'verizonmedia.com', + 'yahoo.com', + 'zeotap.com' +]; + +/* Utility functions */ +function hasPurpose1Consent(bidderRequest) { + if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { + return deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true; + } + } + return true; +} + +function getSize(size) { + return { + w: parseInt(size[0]), + h: parseInt(size[1]) + } +} + +function transformSizes(sizes) { + if (isArray(sizes) && sizes.length === 2 && !isArray(sizes[0])) { + return [ getSize(sizes) ]; + } + return sizes.map(getSize); +} + +function extractUserSyncUrls(syncOptions, pixels) { + let itemsRegExp = /(img|iframe)[\s\S]*?src\s*=\s*("|')(.*?)\2/gi; + let tagNameRegExp = /\w*(?=\s)/; + let srcRegExp = /src=("|')(.*?)\1/; + let userSyncObjects = []; + + if (pixels) { + let matchedItems = pixels.match(itemsRegExp); + if (matchedItems) { + matchedItems.forEach(item => { + let tagName = item.match(tagNameRegExp)[0]; + let url = item.match(srcRegExp)[2]; + + if (tagName && url) { + let tagType = tagName.toLowerCase() === 'img' ? 'image' : 'iframe'; + if ((!syncOptions.iframeEnabled && tagType === 'iframe') || + (!syncOptions.pixelEnabled && tagType === 'image')) { + return; + } + userSyncObjects.push({ + type: tagType, + url: url + }); + } + }); + } + } + + return userSyncObjects; +} + +function getSupportedEids(bid) { + if (isArray(deepAccess(bid, 'userIdAsEids'))) { + return bid.userIdAsEids.filter(eid => { + return SUPPORTED_USER_ID_SOURCES.indexOf(eid.source) !== -1; + }); + } + return []; +} + +function isSecure(bid) { + return deepAccess(bid, 'params.bidOverride.imp.secure') || (document.location.protocol === 'https:') ? 1 : 0; +}; + +function getPubIdMode(bid) { + let pubIdMode; + if (deepAccess(bid, 'params.pubId')) { + pubIdMode = true; + } else if (deepAccess(bid, 'params.dcn') && deepAccess(bid, 'params.pos')) { + pubIdMode = false; + }; + return pubIdMode; +}; + +function getAdapterMode() { + let adapterMode = config.getConfig('yahoossp.mode'); + adapterMode = adapterMode ? adapterMode.toLowerCase() : undefined; + if (typeof adapterMode === 'undefined' || adapterMode === BANNER) { + return BANNER; + } else if (adapterMode === VIDEO) { + return VIDEO; + } else if (adapterMode === 'all') { + return '*'; + } +}; + +function getResponseFormat(bid) { + const adm = bid.adm; + if (adm.indexOf('o2playerSettings') !== -1 || adm.indexOf('YAHOO.VideoPlatform.VideoPlayer') !== -1 || adm.indexOf('AdPlacement') !== -1) { + return BANNER; + } else if (adm.indexOf('VAST') !== -1) { + return VIDEO; + } +}; + +function getFloorModuleData(bid) { + const adapterMode = getAdapterMode(); + const getFloorRequestObject = { + currency: deepAccess(bid, 'params.bidOverride.cur') || DEFAULT_CURRENCY, + mediaType: adapterMode, + size: '*' + }; + return (isFn(bid.getFloor)) ? bid.getFloor(getFloorRequestObject) : false; +}; + +function filterBidRequestByMode(validBidRequests) { + const mediaTypesMode = getAdapterMode(); + let result = []; + if (mediaTypesMode === BANNER) { + result = validBidRequests.filter(bid => { + return Object.keys(bid.mediaTypes).some(item => item === BANNER); + }); + } else if (mediaTypesMode === VIDEO) { + result = validBidRequests.filter(bid => { + return Object.keys(bid.mediaTypes).some(item => item === VIDEO); + }); + } else if (mediaTypesMode === '*') { + result = validBidRequests.filter(bid => { + return Object.keys(bid.mediaTypes).some(item => item === BANNER || item === VIDEO); + }); + }; + return result; +}; + +function validateAppendObject(validationType, allowedKeys, inputObject, appendToObject) { + const outputObject = { + ...appendToObject + }; + + for (const objectKey in inputObject) { + switch (validationType) { + case 'string': + if (allowedKeys.indexOf(objectKey) !== -1 && isStr(inputObject[objectKey])) { + outputObject[objectKey] = inputObject[objectKey]; + }; + break; + case 'number': + if (allowedKeys.indexOf(objectKey) !== -1 && isNumber(inputObject[objectKey])) { + outputObject[objectKey] = inputObject[objectKey]; + }; + break; + + case 'array': + if (allowedKeys.indexOf(objectKey) !== -1 && isArray(inputObject[objectKey])) { + outputObject[objectKey] = inputObject[objectKey]; + }; + break; + case 'object': + if (allowedKeys.indexOf(objectKey) !== -1 && isPlainObject(inputObject[objectKey])) { + outputObject[objectKey] = inputObject[objectKey]; + }; + break; + case 'objectAllKeys': + if (isPlainObject(inputObject)) { + outputObject[objectKey] = inputObject[objectKey]; + }; + break; + }; + }; + return outputObject; +}; + +function getTtl(bidderRequest) { + const globalTTL = config.getConfig('yahoossp.ttl'); + return globalTTL ? validateTTL(globalTTL) : validateTTL(deepAccess(bidderRequest, 'params.ttl')); +}; + +function validateTTL(ttl) { + return (isNumber(ttl) && ttl > 0 && ttl < 3600) ? ttl : DEFAULT_BID_TTL +}; + +function isNotEmptyStr(value) { + return (isStr(value) && value.length > 0); +}; + +function generateOpenRtbObject(bidderRequest, bid) { + if (bidderRequest) { + let outBoundBidRequest = { + id: generateUUID(), + cur: [getFloorModuleData(bidderRequest).currency || deepAccess(bid, 'params.bidOverride.cur') || DEFAULT_CURRENCY], + imp: [], + site: { + page: deepAccess(bidderRequest, 'refererInfo.referer'), + }, + device: { + dnt: 0, + ua: navigator.userAgent, + ip: deepAccess(bid, 'params.bidOverride.device.ip') || deepAccess(bid, 'params.ext.ip') || undefined + }, + regs: { + ext: { + 'us_privacy': bidderRequest.uspConsent ? bidderRequest.uspConsent : '', + gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + } + }, + source: { + ext: { + hb: 1, + adapterver: ADAPTER_VERSION, + prebidver: PREBID_VERSION, + integration: { + name: INTEGRATION_METHOD, + ver: PREBID_VERSION + } + }, + fd: 1 + }, + user: { + ext: { + consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies + ? bidderRequest.gdprConsent.consentString : '', + eids: getSupportedEids(bid) + } + } + }; + + if (getPubIdMode(bid) === true) { + outBoundBidRequest.site.publisher = { + id: bid.params.pubId + } + if (deepAccess(bid, 'params.bidOverride.site.id') || deepAccess(bid, 'params.siteId')) { + outBoundBidRequest.site.id = deepAccess(bid, 'params.bidOverride.site.id') || bid.params.siteId; + } + } else { + outBoundBidRequest.site.id = bid.params.dcn; + }; + + if (config.getConfig('ortb2')) { + outBoundBidRequest = appendFirstPartyData(outBoundBidRequest, bid); + }; + + if (deepAccess(bid, 'schain')) { + outBoundBidRequest.source.ext.schain = bid.schain; + outBoundBidRequest.source.ext.schain.nodes[0].rid = outBoundBidRequest.id; + }; + + return outBoundBidRequest; + }; +}; + +function appendImpObject(bid, openRtbObject) { + const mediaTypeMode = getAdapterMode(); + + if (openRtbObject && bid) { + const impObject = { + id: bid.bidId, + secure: isSecure(bid), + bidfloor: getFloorModuleData(bid).floor || deepAccess(bid, 'params.bidOverride.imp.bidfloor') + }; + + if (bid.mediaTypes.banner && (typeof mediaTypeMode === 'undefined' || mediaTypeMode === BANNER || mediaTypeMode === '*')) { + impObject.banner = { + mimes: bid.mediaTypes.banner.mimes || ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], + format: transformSizes(bid.sizes) + }; + if (bid.mediaTypes.banner.pos) { + impObject.banner.pos = bid.mediaTypes.banner.pos; + }; + }; + + if (bid.mediaTypes.video && (mediaTypeMode === VIDEO || mediaTypeMode === '*')) { + const playerSize = transformSizes(bid.mediaTypes.video.playerSize); + impObject.video = { + mimes: deepAccess(bid, 'params.bidOverride.imp.video.mimes') || bid.mediaTypes.video.mimes || ['video/mp4', 'application/javascript'], + w: deepAccess(bid, 'params.bidOverride.imp.video.w') || playerSize[0].w, + h: deepAccess(bid, 'params.bidOverride.imp.video.h') || playerSize[0].h, + maxbitrate: deepAccess(bid, 'params.bidOverride.imp.video.maxbitrate') || bid.mediaTypes.video.maxbitrate || undefined, + maxduration: deepAccess(bid, 'params.bidOverride.imp.video.maxduration') || bid.mediaTypes.video.maxduration || undefined, + minduration: deepAccess(bid, 'params.bidOverride.imp.video.minduration') || bid.mediaTypes.video.minduration || undefined, + api: deepAccess(bid, 'params.bidOverride.imp.video.api') || bid.mediaTypes.video.api || [2], + delivery: deepAccess(bid, 'params.bidOverride.imp.video.delivery') || bid.mediaTypes.video.delivery || undefined, + pos: deepAccess(bid, 'params.bidOverride.imp.video.pos') || bid.mediaTypes.video.pos || undefined, + playbackmethod: deepAccess(bid, 'params.bidOverride.imp.video.playbackmethod') || bid.mediaTypes.video.playbackmethod || undefined, + placement: deepAccess(bid, 'params.bidOverride.imp.video.placement') || bid.mediaTypes.video.placement || undefined, + linearity: deepAccess(bid, 'params.bidOverride.imp.video.linearity') || bid.mediaTypes.video.linearity || 1, + protocols: deepAccess(bid, 'params.bidOverride.imp.video.protocols') || bid.mediaTypes.video.protocols || [2, 5], + startdelay: deepAccess(bid, 'params.bidOverride.imp.video.startdelay') || bid.mediaTypes.video.startdelay || 0, + rewarded: deepAccess(bid, 'params.bidOverride.imp.video.rewarded') || undefined, + } + } + + impObject.ext = { + dfp_ad_unit_code: bid.adUnitCode + }; + + if (deepAccess(bid, 'params.kvp') && isPlainObject(bid.params.kvp)) { + impObject.ext.kvs = {}; + for (const key in bid.params.kvp) { + if (isStr(bid.params.kvp[key]) || isNumber(bid.params.kvp[key])) { + impObject.ext.kvs[key] = bid.params.kvp[key]; + } else if (isArray(bid.params.kvp[key])) { + const array = bid.params.kvp[key]; + if (array.every(value => isStr(value)) || array.every(value => isNumber(value))) { + impObject.ext.kvs[key] = bid.params.kvp[key]; + } + } + } + }; + + if (deepAccess(bid, 'ortb2Imp.ext.data') && isPlainObject(bid.ortb2Imp.ext.data)) { + impObject.ext.data = bid.ortb2Imp.ext.data; + }; + + if (deepAccess(bid, 'ortb2Imp.instl') && isNumber(bid.ortb2Imp.instl) && (bid.ortb2Imp.instl === 1)) { + impObject.instl = bid.ortb2Imp.instl; + }; + + if (getPubIdMode(bid) === false) { + impObject.tagid = bid.params.pos; + impObject.ext.pos = bid.params.pos; + } else if (deepAccess(bid, 'params.placementId')) { + impObject.tagid = bid.params.placementId + }; + + openRtbObject.imp.push(impObject); + }; +}; + +function appendFirstPartyData(outBoundBidRequest, bid) { + const ortb2Object = config.getConfig('ortb2'); + const siteObject = deepAccess(ortb2Object, 'site') || undefined; + const siteContentObject = deepAccess(siteObject, 'content') || undefined; + const siteContentDataArray = deepAccess(siteObject, 'content.data') || undefined; + const appContentObject = deepAccess(ortb2Object, 'app.content') || undefined; + const appContentDataArray = deepAccess(ortb2Object, 'app.content.data') || undefined; + const userObject = deepAccess(ortb2Object, 'user') || undefined; + + if (siteObject && isPlainObject(siteObject)) { + const allowedSiteStringKeys = ['name', 'domain', 'page', 'ref', 'keywords', 'search']; + const allowedSiteArrayKeys = ['cat', 'sectioncat', 'pagecat']; + const allowedSiteObjectKeys = ['ext']; + outBoundBidRequest.site = validateAppendObject('string', allowedSiteStringKeys, siteObject, outBoundBidRequest.site); + outBoundBidRequest.site = validateAppendObject('array', allowedSiteArrayKeys, siteObject, outBoundBidRequest.site); + outBoundBidRequest.site = validateAppendObject('object', allowedSiteObjectKeys, siteObject, outBoundBidRequest.site); + }; + + if (siteContentObject && isPlainObject(siteContentObject)) { + const allowedContentStringKeys = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language']; + const allowedContentNumberkeys = ['episode', 'prodq', 'context', 'livestream', 'len']; + const allowedContentArrayKeys = ['cat']; + const allowedContentObjectKeys = ['ext']; + outBoundBidRequest.site.content = validateAppendObject('string', allowedContentStringKeys, siteContentObject, outBoundBidRequest.site.content); + outBoundBidRequest.site.content = validateAppendObject('number', allowedContentNumberkeys, siteContentObject, outBoundBidRequest.site.content); + outBoundBidRequest.site.content = validateAppendObject('array', allowedContentArrayKeys, siteContentObject, outBoundBidRequest.site.content); + outBoundBidRequest.site.content = validateAppendObject('object', allowedContentObjectKeys, siteContentObject, outBoundBidRequest.site.content); + + if (siteContentDataArray && isArray(siteContentDataArray)) { + siteContentDataArray.every(dataObject => { + let newDataObject = {}; + const allowedContentDataStringKeys = ['id', 'name']; + const allowedContentDataArrayKeys = ['segment']; + const allowedContentDataObjectKeys = ['ext']; + newDataObject = validateAppendObject('string', allowedContentDataStringKeys, dataObject, newDataObject); + newDataObject = validateAppendObject('array', allowedContentDataArrayKeys, dataObject, newDataObject); + newDataObject = validateAppendObject('object', allowedContentDataObjectKeys, dataObject, newDataObject); + outBoundBidRequest.site.content.data = []; + outBoundBidRequest.site.content.data.push(newDataObject); + }) + }; + }; + + if (appContentObject && isPlainObject(appContentObject)) { + if (appContentDataArray && isArray(appContentDataArray)) { + appContentDataArray.every(dataObject => { + let newDataObject = {}; + const allowedContentDataStringKeys = ['id', 'name']; + const allowedContentDataArrayKeys = ['segment']; + const allowedContentDataObjectKeys = ['ext']; + newDataObject = validateAppendObject('string', allowedContentDataStringKeys, dataObject, newDataObject); + newDataObject = validateAppendObject('array', allowedContentDataArrayKeys, dataObject, newDataObject); + newDataObject = validateAppendObject('object', allowedContentDataObjectKeys, dataObject, newDataObject); + outBoundBidRequest.app = { + content: { + data: [] + } + }; + outBoundBidRequest.app.content.data.push(newDataObject); + }) + }; + }; + + if (userObject && isPlainObject(userObject)) { + const allowedUserStrings = ['id', 'buyeruid', 'gender', 'keywords', 'customdata']; + const allowedUserNumbers = ['yob']; + const allowedUserArrays = ['data']; + const allowedUserObjects = ['ext']; + outBoundBidRequest.user = validateAppendObject('string', allowedUserStrings, userObject, outBoundBidRequest.user); + outBoundBidRequest.user = validateAppendObject('number', allowedUserNumbers, userObject, outBoundBidRequest.user); + outBoundBidRequest.user = validateAppendObject('array', allowedUserArrays, userObject, outBoundBidRequest.user); + outBoundBidRequest.user.ext = validateAppendObject('object', allowedUserObjects, userObject, outBoundBidRequest.user.ext); + }; + + return outBoundBidRequest; +}; + +function generateServerRequest({payload, requestOptions, bidderRequest}) { + const pubIdMode = getPubIdMode(bidderRequest); + let sspEndpoint = config.getConfig('yahoossp.endpoint') || SSP_ENDPOINT_DCN_POS; + + if (pubIdMode === true) { + sspEndpoint = config.getConfig('yahoossp.endpoint') || SSP_ENDPOINT_PUBID; + }; + + if (deepAccess(bidderRequest, 'params.testing.e2etest') === true) { + logInfo('yahoossp adapter e2etest mode is active'); + requestOptions.withCredentials = false; + + if (pubIdMode === true) { + payload.site.id = TEST_MODE_PUBID_DCN; + } else { + const mediaTypeMode = getAdapterMode(); + payload.site.id = TEST_MODE_DCN; + payload.imp.forEach(impObject => { + impObject.ext.e2eTestMode = true; + if (mediaTypeMode === BANNER) { + impObject.tagid = TEST_MODE_BANNER_POS; // banner passback + } else if (mediaTypeMode === VIDEO) { + impObject.tagid = TEST_MODE_VIDEO_POS; // video passback + } else { + logWarn('yahoossp adapter e2etest mode does not support yahoossp.mode="all". \n Please specify either "banner" or "video"'); + logWarn('yahoossp adapter e2etest mode: Please make sure your adUnit matches the yahoossp.mode video or banner'); + } + }); + } + }; + logWarn('yahoossp adapter endpoint override enabled. Pointing requests to: ', sspEndpoint); + + return { + url: sspEndpoint, + method: 'POST', + data: payload, + options: requestOptions, + bidderRequest: bidderRequest + }; +}; + +function createRenderer(bidderRequest, bidResponse) { + const renderer = Renderer.install({ + url: 'https://cdn.vidible.tv/prod/hb-outstream-renderer/renderer.js', + loaded: false, + adUnitCode: bidderRequest.adUnitCode + }) + + try { + renderer.setRender(function(bidResponse) { + setTimeout(function() { + // eslint-disable-next-line no-undef + o2PlayerRender(bidResponse); + }, deepAccess(bidderRequest, 'params.testing.renderer.setTimeout') || DEFAULT_RENDERER_TIMEOUT); + }); + } catch (error) { + logWarn('yahoossp renderer error: setRender() failed', error); + } + return renderer; +} +/* Utility functions */ + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + aliases: [], + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: function(bid) { + const params = bid.params; + if (deepAccess(params, 'testing.e2etest') === true) { + return true; + } else if ( + isPlainObject(params) && + (isNotEmptyStr(params.pubId) || (isNotEmptyStr(params.dcn) && isNotEmptyStr(params.pos))) + ) { + return true; + } else { + logWarn('yahoossp bidder params missing or incorrect, please pass object with either: dcn & pos OR pubId'); + return false; + } + }, + + buildRequests: function(validBidRequests, bidderRequest) { + if (isEmpty(validBidRequests) || isEmpty(bidderRequest)) { + logWarn('yahoossp Adapter: buildRequests called with either empty "validBidRequests" or "bidderRequest"'); + return undefined; + }; + + const requestOptions = { + contentType: 'application/json', + customHeaders: { + 'x-openrtb-version': '2.5' + } + }; + + requestOptions.withCredentials = hasPurpose1Consent(bidderRequest); + + const filteredBidRequests = filterBidRequestByMode(validBidRequests); + + if (config.getConfig('yahoossp.singleRequestMode') === true) { + const payload = generateOpenRtbObject(bidderRequest, filteredBidRequests[0]); + filteredBidRequests.forEach(bid => { + appendImpObject(bid, payload); + }); + + return generateServerRequest({payload, requestOptions, bidderRequest}); + } + + return filteredBidRequests.map(bid => { + const payloadClone = generateOpenRtbObject(bidderRequest, bid); + appendImpObject(bid, payloadClone); + return generateServerRequest({payload: payloadClone, requestOptions, bidderRequest: bid}); + }); + }, + + interpretResponse: function(serverResponse, { data, bidderRequest }) { + const response = []; + if (!serverResponse.body || !Array.isArray(serverResponse.body.seatbid)) { + return response; + } + + let seatbids = serverResponse.body.seatbid; + seatbids.forEach(seatbid => { + let bid; + + try { + bid = seatbid.bid[0]; + } catch (e) { + return response; + } + + let cpm = (bid.ext && bid.ext.encp) ? bid.ext.encp : bid.price; + + let bidResponse = { + adId: deepAccess(bid, 'adId') ? bid.adId : bid.impid || bid.crid, + adUnitCode: bidderRequest.adUnitCode, + requestId: bid.impid, + bidderCode: spec.code, + cpm: cpm, + width: bid.w, + height: bid.h, + creativeId: bid.crid || 0, + currency: bid.cur || DEFAULT_CURRENCY, + dealId: bid.dealid ? bid.dealid : null, + netRevenue: true, + ttl: getTtl(bidderRequest), + meta: { + advertiserDomains: bid.adomain, + } + }; + + const responseAdmFormat = getResponseFormat(bid); + if (responseAdmFormat === BANNER) { + bidResponse.mediaType = BANNER; + bidResponse.ad = bid.adm; + bidResponse.meta.mediaType = BANNER; + } else if (responseAdmFormat === VIDEO) { + bidResponse.mediaType = VIDEO; + bidResponse.meta.mediaType = VIDEO; + bidResponse.vastXml = bid.adm; + + if (bid.nurl) { + bidResponse.vastUrl = bid.nurl; + }; + } + + if (deepAccess(bidderRequest, 'mediaTypes.video.context') === 'outstream' && !bidderRequest.renderer) { + bidResponse.renderer = createRenderer(bidderRequest, bidResponse) || undefined; + } + + response.push(bidResponse); + }); + + return response; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const bidResponse = !isEmpty(serverResponses) && serverResponses[0].body; + + if (bidResponse && bidResponse.ext && bidResponse.ext.pixels) { + return extractUserSyncUrls(syncOptions, bidResponse.ext.pixels); + } + + return []; + } +}; + +registerBidder(spec); diff --git a/modules/yahoosspBidAdapter.md b/modules/yahoosspBidAdapter.md new file mode 100644 index 00000000000..7fb7a307192 --- /dev/null +++ b/modules/yahoosspBidAdapter.md @@ -0,0 +1,795 @@ +# Overview +**Module Name:** yahoossp Bid Adapter +**Module Type:** Bidder Adapter +**Maintainer:** hb-fe-tech@yahooinc.com + +# Description +The Yahoo SSP Bid Adapter is an OpenRTB interface that consolidates all previous "Oath.inc" adapters such as: "aol", "oneMobile", "oneDisplay" & "oneVideo" supply-side platforms. + +# Supported Features: +* Media Types: Banner & Video +* Outstream renderer +* Multi-format adUnits +* Schain module +* Price floors module +* Advertiser domains +* End-2-End self-served testing mode +* Outstream renderer/Player +* User ID Modules - ConnectId and others +* First Party Data (ortb2 & ortb2Imp) +* Custom TTL (time to live) + + +# Adapter Request mode +Since the yahoossp adapter now supports both Banner and Video adUnits a controller was needed to allow you to define when the adapter should generate a bid-requests to our Yahoo SSP. + +**Important!** By default the adapter mode is set to "banner" only. +This means that you do not need to explicitly declare the yahoossp.mode in the Global config to initiate banner adUnit requests. + +## Request modes: +* **undefined** - (Default) Will generate bid-requests for "Banner" formats only. +* **banner** - Will generate bid-requests for "Banner" formats only (Explicit declaration). +* **video** - Will generate bid-requests for "Video" formats only (Explicit declaration). +* **all** - Will generate bid-requests for both "Banner" & "Video" formats + +**Important!** When setting yahoossp.mode = 'all' Make sure your Yahoo SSP Placement (pos id) supports both Banner & Video placements. +If it does not, the Yahoo SSP will respond only in the format it is set too. + +```javascript +pbjs.setConfig({ + yahoossp: { + mode: 'banner' // 'all', 'video', 'banner' (default) + } +}); +``` +# Integration Options +The `yahoossp` bid adapter supports 2 types of integration: +1. **dcn & pos** DEFAULT (Site/App & Position targeting) - For Display partners/publishers. +2. **pubId** (Publisher ID) - For legacy "oneVideo" AND New partners/publishers. +**Important:** pubId integration (option 2) is only possible when your Seller account is setup for "Inventory Mapping". + +**Please Note:** Most examples in this file are using dcn & pos. + +## Who is currently eligible for "pubId" integration +At this time, only the following partners/publishers are eligble for pubId integration: +1. New partners/publishers that do not have any existing accounts on Yahoo SSP (aka: aol, oneMobile, oneDisplay). +2. Video SSP (oneVideo) partners/publishers that + A. Do not have any display/banner inventory. + B. Do not have any existing accounts on Yahoo SSP (aka: aol, oneMobile, oneDisplay). + +# Mandatory Bidder Parameters +## dcn & pos (DEFAULT) +The minimal requirements for the 'yahoossp' bid adapter to generate an outbound bid-request to our Yahoo SSP are: +1. At least 1 adUnit including mediaTypes: banner or video +2. **bidder.params** object must include: + A. **dcn:** Yahoo SSP Site/App inventory parameter. + B. **pos:** Yahoo SSP position inventory parameter. + +### Example: dcn & pos Mandatory Parameters (Single banner adUnit) +```javascript +const adUnits = [{ + code: 'your-placement', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'yahoossp', + params: { + dcn: '8a969516017a7a396ec539d97f540011', // Site/App ID provided from SSP + pos: '8a969978017a7aaabab4ab0bc01a0009' // Placement ID provided from SSP + } + } + ] +}]; +``` + +## pubId +The minimal requirements for the 'yahoossp' bid adapter to generate an outbound bid-request to our Yahoo SSP are: +1. At least 1 adUnit including mediaTypes: banner or video +2. **bidder.params** object must include: + A. **pubId:** Yahoo SSP Publisher ID (AKA oneVideo pubId/Exchange name) + +### Example: pubId Mandatory Parameters (Single banner adUnit) +```javascript +const adUnits = [{ + code: 'your-placement', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'yahoossp', + params: { + pubId: 'DemoPublisher', // Publisher External ID provided from Yahoo SSP. + } + } + ] +}]; +``` +# Advanced adUnit Examples: +## Banner +```javascript +const adUnits = [{ + code: 'banner-adUnit', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + dcn: '8a969516017a7a396ec539d97f540011', // Site/App ID provided from Yahoo SSP + pos: '8a969978017a7aaabab4ab0bc01a0009', // Placement ID provided from Yahoo SSP + } + } + }] +}]; +``` +## Video Instream +**Important!** Make sure that the Yahoo SSP Placement type (in-stream) matches the adUnit video inventory type. +**Note:** Make sure to set the adapter mode to allow video requests by setting it to mode: 'video' OR mode: 'all'. +```javascript +pbjs.setConfig({ + yahoossp: { + mode: 'video' + } +}); + +const adUnits = [{ + code: 'video-adUnit', + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [300, 250] + ], + mimes: ['video/mp4','application/javascript'], + api: [2] + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + dcn: '8a969516017a7a396ec539d97f540011', // Site/App ID provided from Yahoo SSP + pos: '8a96958a017a7a57ac375d50c0c700cc', // Placement ID provided from Yahoo SSP + } + }] +}]; +``` +## Video Outstream +**Important!** Make sure that the Yahoo SSP Placement type (in-feed/ in-article) matches the adUnit video inventory type. +**Note:** Make sure to set the adapter mode to allow video requests by setting it to mode: 'video' OR mode: 'all' +```javascript +pbjs.setConfig({ + yahoossp: { + mode: 'video' + } +}); + +const adUnits = [{ + code: 'video-adUnit', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ + [300, 250] + ], + mimes: ['video/mp4','application/javascript'], + api: [2] + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + dcn: '8a969516017a7a396ec539d97f540011', // Site/App ID provided from Yahoo SSP + pos: '8a96958a017a7a57ac375d50c0c700cc', // Placement ID provided from Yahoo SSP + } + }] +}]; +``` +## Multi-Format +**Important!** If you intend to use the yahoossp bidder for both Banner and Video formats please make sure: +1. Set the adapter as mode: 'all' - to call the Yahoo SSP for both banner & video formats. +2. Make sure the Yahoo SSP placement (pos id) supports both banner & video format requests. + +```javascript +pbjs.setConfig({ + yahoossp: { + mode: 'all' + } +}); + +const adUnits = [{ + code: 'video-adUnit', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + context: 'outstream', + playerSize: [ + [300, 250] + ], + mimes: ['video/mp4','application/javascript'], + api: [2] + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + dcn: '8a969516017a7a396ec539d97f540011', // Site/App ID provided from Yahoo SSP + pos: '8a96958a017a7a57ac375d50c0c700cc', // Placement ID provided from Yahoo SSP + } + }] +}]; +``` + +# Optional: Schain module support +The yahoossp adapter supports the Prebid.org Schain module and will pass it through to our Yahoo SSP +For further details please see, https://docs.prebid.org/dev-docs/modules/schain.html + +## Global Schain Example: +```javascript + pbjs.setConfig({ + "schain": { + "validation": "off", + "config": { + "ver": "1.0", + "complete": 1, + "nodes": [{ + "asi": "some-platform.com", + "sid": "111111", + "hp": 1 + }] + } + } + }); +``` +## Bidder Specific Schain Example: +```javascript + pbjs.setBidderConfig({ + "bidders": ['yahoossp'], // can list more bidders here if they share the same config + "config": { + "schain": { + "validation": "strict", + "config": { + "ver": "1.0", + "complete": 1, + "nodes": [{ + "asi": "other-platform.com", + "sid": "222222", + "hp": 0 + }] + } + } + } + }); +``` + +# Optional: Price floors module & bidfloor +The yahoossp adapter supports the Prebid.org Price Floors module and will use it to define the outbound bidfloor and currency. +By default the adapter will always check the existance of Module price floor. +If a module price floor does not exist you can set a custom bid floor for your impression using "params.bidOverride.imp.bidfloor". + +**Note:** All override params apply to all requests generated using this configuration regardless of format type. + +```javascript +const adUnits = [{ + code: 'override-pricefloor', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + dcn: '8a969516017a7a396ec539d97f540011', // Site/App ID provided from Yahoo SSP + pos: '8a969978017a7aaabab4ab0bc01a0009', // Placement ID provided from Yahoo SSP + bidOverride :{ + imp: { + bidfloor: 5.00 // bidOverride priceFloor + } + } + } + } + }] +}]; +``` + +For further details please see, https://docs.prebid.org/dev-docs/modules/floors.html + +# Optional: Self-served E2E testing mode +If you want to see how the yahoossp adapter works and loads you are invited to try it out using our testing mode. +This is useful for integration testing and response parsing when checking banner vs video capabilities. + +## How to use E2E test mode: +1. Set the yahoossp global config mode to either 'banner' or 'video' - depending on the adUnit you want to test. +2. Add params.testing.e2etest: true to your adUnit bidder config - See examples below. + +**Note:** When using E2E Test Mode you do not need to pass mandatory bidder params dcn or pos. + +**Important!** E2E Testing Mode only works when the Bidder Request Mode is set explicitly to either 'banner' or 'video'. + +## Activating E2E Test for "Banner" + ```javascript +pbjs.setConfig({ + yahoossp: { + mode: 'banner' // select 'banner' or 'video' to define what response to load + } +}); + +const adUnits = [{ + code: 'your-placement', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'yahoossp', + params: { + testing: { + e2etest: true // Activate E2E Test mode + } + } + } + ] +}]; + +``` +## Activating E2E Test for "Video" +**Note:** We recommend using Video Outstream as it would load the video response using our Outstream Renderer feature + ```javascript +pbjs.setConfig({ + yahoossp: { + mode: 'video' + } +}); + +const adUnits = [{ + code: 'video-adUnit', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ + [300, 250] + ], + mimes: ['video/mp4','application/javascript'], + api: [2] + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + testing: { + e2etest: true // Activate E2E Test mode + } + } + }] +}]; +``` + +# Optional: First Party Data +The yahoossp adapter now supports first party data passed via: +1. Global ortb2 object using pbjs.setConfig() +2. adUnit ortb2Imp object declared within an adUnit. +For further details please see, https://docs.prebid.org/features/firstPartyData.html +## Global First Party Data "ortb2" +### Passing First Party "site" data: +```javascript +pbjs.setConfig({ + ortb2: { + site: { + name: 'yahooAdTech', + domain: 'yahooadtech.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'], + page: 'https://page.yahooadtech.com/here.html', + ref: 'https://ref.yahooadtech.com/there.html', + keywords:'yahoo, ad, tech', + search: 'SSP', + content: { + id: '1234', + title: 'Title', + series: 'Series', + season: 'Season', + episode: 1, + cat: ['IAB1', 'IAB1-1', 'IAB1-2', 'IAB2', 'IAB2-1'], + genre: 'Fantase', + contentrating: 'C-Rating', + language: 'EN', + prodq: 1, + context: 1, + len: 200, + data: [{ + name: "www.dataprovider1.com", + ext: { segtax: 4 }, + segment: [ + { id: "687" }, + { id: "123" } + ] + }], + ext: { + network: 'ext-network', + channel: 'ext-channel', + data: { + pageType: "article", + category: "repair" + } + } + } + } + } + }); +``` +### Passing First Party "user" data: +```javascript +pbjs.setConfig({ + ortb2: { + user: { + yob: 1985, + gender: 'm', + keywords: 'a,b', + data: [{ + name: "www.dataprovider1.com", + ext: { segtax: 4 }, + segment: [ + { id: "687" }, + { id: "123" } + ] + }], + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + }); +``` +### Passing First Party "app.content.data" data: +```javascript +pbjs.setConfig({ + ortb2: { + app: { + content: { + data: [{ + name: "www.dataprovider1.com", + ext: { segtax: 4 }, + segment: [ + { id: "687" }, + { id: "123" } + ] + }], + } + } + } + }); +``` + + +## AdUnit First Party Data "ortb2Imp" +Most DSPs are adopting the Global Placement ID (GPID). +Please pass your placement specific GPID value to Yahoo SSP using `adUnit.ortb2Imp.ext.data.pbadslot`. +```javascript +const adUnits = [{ + code: 'placement', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + }, + ortb2Imp: { + ext: { + data: { + pbadslot: "homepage-top-rect", + adUnitSpecificAttribute: "123" + } + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + pubdId: 'DemoPublisher' + } + }] + } + ] +``` + +# Optional: Bidder bidOverride Parameters +The yahoossp adapter allows passing override data to the outbound bid-request in that overrides First Party Data. +**Important!** We highly recommend using prebid modules to pass data instead of bidder speicifc overrides. +The use of these parameters are a last resort to force a specific feature or use case in your implementation. + +Currently the bidOverride object only accepts the following: +* imp + * video + * mimes + * w + * h + * maxbitrate + * maxduration + * minduration + * api + * delivery + * pos + * playbackmethod + * placement + * linearity + * protocols + * rewarded +* site + * page +* device + * ip + + +```javascript +const adUnits = [{ + code: 'bidOverride-adUnit', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ + [300, 250] + ], + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + dcn: '8a969516017a7a396ec539d97f540011', + pos: '8a96958a017a7a57ac375d50c0c700cc', + bidOverride: { + imp: { + video: { + mimes: ['video/mp4', 'javascript/application'], + w: 300, + h: 200, + maxbitrate: 4000, + maxduration: 30, + minduration: 10, + api: [1,2], + delivery: 1, + pos: 1, + playbackmethod: 0, + placement: 1, + linearity: 1, + protocols: [2,5], + startdelay: 0, + rewarded: 0 + } + }, + site: { + page: 'https://yahoossp-bid-adapter.com', + }, + device: { + ip: "1.2.3.4" + } + } + } + }] +}] +``` + +# Optional: Custom Key-Value Pairs +Custom key-value paris can be used for both inventory targeting and reporting. +You must set up key-value pairs in the Yahoo SSP before sending them via the adapter otherwise the Ad Server will not be listening and picking them up. + +Important! Key-value pairs can only contain values of types: String, Number, Array of strings OR Array of numbers + +```javascript +const adUnits = [{ + code: 'key-value-pairs', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ + [300, 250] + ], + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + dcn: '8a969516017a7a396ec539d97f540011', + pos: '8a96958a017a7a57ac375d50c0c700cc', + kvp: { + key1: 'value', // String + key2: 123456, // Number + key3: ['string1','string2', 'string3'], // Array of strings + key4: [1, 23, 456, 7890] // Array of Numbers + } + } + }] +}] +``` + +# Optional: Custom Cache Time To Live (ttl): +The yahoossp adapter supports passing of "Time To Live" (ttl) that indicates to prebid chache for how long to keep the chaced winning bid alive. Value is Number in seconds and you can enter any number between 1 - 3600 (seconds). +The setting can be defined globally using setConfig or within the adUnit.params. +Global level setConfig overrides adUnit.params. +If no value is being passed default is 300 seconds. +## Global TTL +```javascript +pbjs.setConfig({ + yahoossp: { + ttl: 300 + } +}); + +const adUnits = [{ + code: 'global-ttl', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ + [300, 250] + ], + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + dcn: '8a969516017a7a396ec539d97f540011', + pos: '8a96958a017a7a57ac375d50c0c700cc', + } + }] +}] +``` + +## Ad Unit Params TTL +```javascript +const adUnits = [{ + code: 'adUnit-ttl', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + dcn: '8a969516017a7a396ec539d97f540011', + pos: '8a96958a017a7a57ac375d50c0c700cc', + ttl: 300, + } + }] +}] +``` +# Optional: Video Features +## Rewarded video flag +To indicate to Yahoo SSP that this adUnit is a rewarded video you can pass the following in the params.bidOverride.imp.video.rewarded: 1 + +```javascript +const adUnits = [{ + code: 'rewarded-video-adUnit', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ + [300, 250] + ], + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + dcn: '8a969516017a7a396ec539d97f540011', + pos: '8a96958a017a7a57ac375d50c0c700cc', + bidOverride: { + imp: { + video: { + rewarded: 1 + } + } + } + } + }] +}] +``` + +## Site/App Targeting for "pubId" Inventory Mapping +To target your adUnit explicitly to a specific Site/App Object in Yahoo SSP, you can pass one of the following: +1. params.siteId = External Site ID || Video SSP RTBIS Id (in String format). +2. params.bidOverride.site.id = External Site ID || Video SSP RTBIS Id (in String format). +**Important:** Site override is a only supported when using "pubId" mode. +**Important:** If you are switching from the oneVideo adapter, please make sure to pass inventoryid as a String instead of Integer. + +```javascript +const adUnits = [{ + code: 'pubId-site-targeting-adUnit', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ + [300, 250] + ], + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + pubId: 'DemoPublisher', + siteId: '1234567'; + } + }] +}] +``` + +## Placement Targeting for "pubId" Inventory Mapping +To target your adUnit explicitly to a specific Placement within a Site/App Object in Yahoo SSP, you can pass the following params.placementId = External Placement ID || Placement Alias + +**Important!** Placement override is a only supported when using "pubId" mode. +**Important!** It is highly recommended that you pass both `siteId` AND `placementId` together to avoid inventory miss matching. + +### Site & Placement override +**Important!** If the placement ID does not reside under the defined Site/App object, the request will not resolve and no response will be sent back from the ad-server. +```javascript +const adUnits = [{ + code: 'pubId-site-targeting-adUnit', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ + [300, 250] + ], + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + pubId: 'DemoPublisher', + siteId: '1234567', + placementId: 'header-250x300' + } + }] +}] +``` +### Placement only override +**Important!** Using this method is not advised if you have multiple Site/Apps that are broken out of a Run Of Network (RON) Site/App. If the placement ID does not reside under a matching Site/App object, the request will not resolve and no response will be sent back from the ad-server. +```javascript +const adUnits = [{ + code: 'pubId-site-targeting-adUnit', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ + [300, 250] + ], + } + }, + bids: [{ + bidder: 'yahoossp', + params: { + pubId: 'DemoPublisher', + placementId: 'header-250x300' + } + }] +}] +``` + +# Optional: Legacy override Parameters +This adapter does not support passing legacy overrides via 'bidder.params.ext' since most of the data should be passed using prebid modules (First Party Data, Schain, Price Floors etc.). +If you do not know how to pass a custom parameter that you previously used, please contact us using the information provided above. + +Thanks you, +Yahoo SSP + diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 9c1c54cfb2b..c2f2b79a3b7 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -1,18 +1,21 @@ -import * as utils from '../src/utils.js' +import { _each, isPlainObject, isArray, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import find from 'core-js-pure/features/array/find.js' -import { VIDEO, BANNER } from '../src/mediaTypes.js' +import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js' import { Renderer } from '../src/Renderer.js' +import { config } from '../src/config.js'; const ENDPOINT = 'https://ad.yieldlab.net' const BIDDER_CODE = 'yieldlab' const BID_RESPONSE_TTL_SEC = 300 const CURRENCY_CODE = 'EUR' const OUTSTREAMPLAYER_URL = 'https://ad.adition.com/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event' +const GVLID = 70 export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [VIDEO, BANNER], + gvlid: GVLID, + supportedMediaTypes: [VIDEO, BANNER, NATIVE], isBidRequestValid: function (bid) { if (bid && bid.params && bid.params.adslotId && bid.params.supplyId) { @@ -34,7 +37,7 @@ export const spec = { json: true } - utils._each(validBidRequests, function (bid) { + _each(validBidRequests, function (bid) { adslotIds.push(bid.params.adslotId) if (bid.params.targeting) { query.t = createTargetingString(bid.params.targeting) @@ -42,14 +45,19 @@ export const spec = { if (bid.userIdAsEids && Array.isArray(bid.userIdAsEids)) { query.ids = createUserIdString(bid.userIdAsEids) } - if (bid.params.customParams && utils.isPlainObject(bid.params.customParams)) { + if (bid.params.customParams && isPlainObject(bid.params.customParams)) { for (let prop in bid.params.customParams) { query[prop] = bid.params.customParams[prop] } } - if (bid.schain && utils.isPlainObject(bid.schain) && Array.isArray(bid.schain.nodes)) { + if (bid.schain && isPlainObject(bid.schain) && Array.isArray(bid.schain.nodes)) { query.schain = createSchainString(bid.schain) } + + const iabContent = getContentObject(bid) + if (iabContent) { + query.iab_content = createIabContentString(iabContent) + } }) if (bidderRequest) { @@ -96,12 +104,14 @@ export const spec = { }) if (matchedBid) { - const adUnitSize = bidRequest.sizes.length === 2 && !utils.isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0] + const adUnitSize = bidRequest.sizes.length === 2 && !isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0] const adSize = bidRequest.params.adSize !== undefined ? parseSize(bidRequest.params.adSize) : (matchedBid.adsize !== undefined) ? parseSize(matchedBid.adsize) : adUnitSize const extId = bidRequest.params.extId !== undefined ? '&id=' + bidRequest.params.extId : '' const adType = matchedBid.adtype !== undefined ? matchedBid.adtype : '' const gdprApplies = reqParams.gdpr ? '&gdpr=' + reqParams.gdpr : '' const gdprConsent = reqParams.consent ? '&consent=' + reqParams.consent : '' + const pvId = matchedBid.pvid !== undefined ? '&pvid=' + matchedBid.pvid : '' + const iabContent = reqParams.iab_content ? '&iab_content=' + reqParams.iab_content : '' const bidResponse = { requestId: bidRequest.bidId, @@ -114,7 +124,10 @@ export const spec = { netRevenue: false, ttl: BID_RESPONSE_TTL_SEC, referrer: '', - ad: `` + ad: ``, + meta: { + advertiserDomains: (matchedBid.advertiser) ? matchedBid.advertiser : 'n/a' + } } if (isVideo(bidRequest, adType)) { @@ -124,7 +137,7 @@ export const spec = { bidResponse.height = playersize[1] } bidResponse.mediaType = VIDEO - bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}` + bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}${iabContent}` if (isOutstream(bidRequest)) { const renderer = Renderer.install({ id: bidRequest.bidId, @@ -136,6 +149,27 @@ export const spec = { } } + if (isNative(bidRequest, adType)) { + const url = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}` + bidResponse.adUrl = url + bidResponse.mediaType = NATIVE + const nativeImageAssetObj = find(matchedBid.native.assets, e => e.id === 2) + const nativeImageAsset = nativeImageAssetObj ? nativeImageAssetObj.img : {url: '', w: 0, h: 0}; + const nativeTitleAsset = find(matchedBid.native.assets, e => e.id === 1) + const nativeBodyAsset = find(matchedBid.native.assets, e => e.id === 3) + bidResponse.native = { + title: nativeTitleAsset ? nativeTitleAsset.title.text : '', + body: nativeBodyAsset ? nativeBodyAsset.data.value : '', + image: { + url: nativeImageAsset.url, + width: nativeImageAsset.w, + height: nativeImageAsset.h, + }, + clickUrl: matchedBid.native.link.url, + impressionTrackers: matchedBid.native.imptrackers, + }; + } + bidResponses.push(bidResponse) } }) @@ -149,8 +183,18 @@ export const spec = { * @param {String} adtype * @returns {Boolean} */ -function isVideo (format, adtype) { - return utils.deepAccess(format, 'mediaTypes.video') && adtype.toLowerCase() === 'video' +function isVideo(format, adtype) { + return deepAccess(format, 'mediaTypes.video') && adtype.toLowerCase() === 'video' +} + +/** + * Is this a native format? + * @param {Object} format + * @param {String} adtype + * @returns {Boolean} + */ +function isNative(format, adtype) { + return deepAccess(format, 'mediaTypes.native') && adtype.toLowerCase() === 'native' } /** @@ -158,8 +202,8 @@ function isVideo (format, adtype) { * @param {Object} format * @returns {Boolean} */ -function isOutstream (format) { - let context = utils.deepAccess(format, 'mediaTypes.video.context') +function isOutstream(format) { + let context = deepAccess(format, 'mediaTypes.video.context') return (context === 'outstream') } @@ -168,9 +212,9 @@ function isOutstream (format) { * @param {Object} format * @returns {Array} */ -function getPlayerSize (format) { - let playerSize = utils.deepAccess(format, 'mediaTypes.video.playerSize') - return (playerSize && utils.isArray(playerSize[0])) ? playerSize[0] : playerSize +function getPlayerSize(format) { + let playerSize = deepAccess(format, 'mediaTypes.video.playerSize') + return (playerSize && isArray(playerSize[0])) ? playerSize[0] : playerSize } /** @@ -178,7 +222,7 @@ function getPlayerSize (format) { * @param {String} size * @returns {Array} */ -function parseSize (size) { +function parseSize(size) { return size.split('x').map(Number) } @@ -187,7 +231,7 @@ function parseSize (size) { * @param {Array} eids * @returns {String} */ -function createUserIdString (eids) { +function createUserIdString(eids) { let str = [] for (let i = 0; i < eids.length; i++) { str.push(eids[i].source + ':' + eids[i].uids[0].id) @@ -200,12 +244,12 @@ function createUserIdString (eids) { * @param {Object} obj * @returns {String} */ -function createQueryString (obj) { +function createQueryString(obj) { let str = [] for (var p in obj) { if (obj.hasOwnProperty(p)) { let val = obj[p] - if (p !== 'schain') { + if (p !== 'schain' && p !== 'iab_content') { str.push(encodeURIComponent(p) + '=' + encodeURIComponent(val)) } else { str.push(p + '=' + val) @@ -220,7 +264,7 @@ function createQueryString (obj) { * @param {Object} obj * @returns {String} */ -function createTargetingString (obj) { +function createTargetingString(obj) { let str = [] for (var p in obj) { if (obj.hasOwnProperty(p)) { @@ -237,7 +281,7 @@ function createTargetingString (obj) { * @param {Object} schain * @returns {String} */ -function createSchainString (schain) { +function createSchainString(schain) { const ver = schain.ver || '' const complete = (schain.complete === 1 || schain.complete === 0) ? schain.complete : '' const keys = ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext'] @@ -247,6 +291,44 @@ function createSchainString (schain) { return `${ver},${complete}${nodesString}` } +/** + * Get content object from bid request + * First get content from bidder params; + * If not provided in bidder params, get from first party data under 'ortb2.site.content' or 'ortb2.app.content' + * @param {Object} bid + * @returns {Object} + */ +function getContentObject(bid) { + if (bid.params.iabContent && isPlainObject(bid.params.iabContent)) { + return bid.params.iabContent + } + + const globalContent = config.getConfig('ortb2.site') ? config.getConfig('ortb2.site.content') + : config.getConfig('ortb2.app.content') + if (globalContent && isPlainObject(globalContent)) { + return globalContent + } + return undefined +} + +/** + * Creates a string for iab_content object + * @param {Object} iabContent + * @returns {String} + */ +function createIabContentString(iabContent) { + const arrKeys = ['keywords', 'cat'] + let str = [] + for (let key in iabContent) { + if (iabContent.hasOwnProperty(key)) { + const value = (arrKeys.indexOf(key) !== -1 && Array.isArray(iabContent[key])) + ? iabContent[key].map(node => encodeURIComponent(node)).join('|') : encodeURIComponent(iabContent[key]) + str.push(''.concat(key, ':', value)) + } + } + return encodeURIComponent(str.join(',')) +} + /** * Encodes URI Component with exlamation mark included. Needed for schain object. * @param {String} str diff --git a/modules/yieldlabBidAdapter.md b/modules/yieldlabBidAdapter.md index a7a3f2715dc..1f52e26f5c7 100644 --- a/modules/yieldlabBidAdapter.md +++ b/modules/yieldlabBidAdapter.md @@ -11,38 +11,96 @@ Maintainer: solutions@yieldlab.de Module that connects to Yieldlab's demand sources # Test Parameters + +```javascript +const adUnits = [ + { + code: 'banner', + sizes: [ [ 728, 90 ] ], + bids: [{ + bidder: 'yieldlab', + params: { + adslotId: '5220336', + supplyId: '1381604', + targeting: { + key1: 'value1', + key2: 'value2' + }, + extId: 'abc', + iabContent: { + id: 'some_id', + episode: '1', + title: 'some title', + series: 'some series', + season: 's1', + artist: 'John Doe', + genre: 'some genre', + isrc: 'CC-XXX-YY-NNNNN', + url: 'http://foo_url.de', + cat: [ 'IAB1-1', 'IAB1-2', 'IAB2-10' ], + context: '7', + keywords: ['k1', 'k2'], + live: '0' + } + } + }] + }, + { + code: 'video', + sizes: [ [ 640, 480 ] ], + mediaTypes: { + video: { + context: 'instream' // or 'outstream' + } + }, + bids: [{ + bidder: 'yieldlab', + params: { + adslotId: '5220339', + supplyId: '1381604' + } + }] + }, + { + code: 'native', + mediaTypes: { + native: { + // native config + } + }, + bids: [{ + bidder: 'yieldlab', + params: { + adslotId: '5220339', + supplyId: '1381604' + } + }] + } +]; ``` - var adUnits = [ - { - code: "banner", - sizes: [[728, 90]], - bids: [{ - bidder: "yieldlab", - params: { - adslotId: "5220336", - supplyId: "1381604", - targeting: { - key1: "value1", - key2: "value2" - }, - extId: "abc" - } - }] - }, { - code: "video", - sizes: [[640, 480]], - mediaTypes: { - video: { - context: "instream" // or "outstream" - } - }, - bids: [{ - bidder: "yieldlab", - params: { - adslotId: "5220339", - supplyId: "1381604" - } - }] - } - ]; + +# Multi-Format Setup + +A general overview of how to set up multi-format ads can be found in the offical Prebid.js docs. See: [show multi-format ads](https://docs.prebid.org/dev-docs/show-multi-format-ads.html) + +When setting up multi-format ads with Yieldlab make sure to always add at least one eligible Adslot per given media type in the ad unit configuration. + +```javascript +const adUnit = { + code: 'multi-format-adslot', + mediaTypes: { + banner: { + sizes: [ [ 728, 90 ] ] + }, + native: { + // native config + } + }, + bids: [ + // banner Adslot + { bidder: 'yieldlab', params: { adslotId: '1234', supplyId: '42' } }, + // native Adslot + { bidder: 'yieldlab', params: { adslotId: '2345', supplyId: '42' } } + ] +}; ``` diff --git a/modules/yieldliftBidAdapter.js b/modules/yieldliftBidAdapter.js index 87a598bd335..61b99d95605 100644 --- a/modules/yieldliftBidAdapter.js +++ b/modules/yieldliftBidAdapter.js @@ -1,5 +1,5 @@ +import { deepSetValue, logInfo, deepAccess } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import * as utils from '../src/utils.js'; import {BANNER} from '../src/mediaTypes.js'; const ENDPOINT_URL = 'https://x.yieldlift.com/auction'; @@ -58,18 +58,18 @@ export const spec = { // adding schain object if (validBidRequests[0].schain) { - utils.deepSetValue(openrtbRequest, 'source.ext.schain', validBidRequests[0].schain); + deepSetValue(openrtbRequest, 'source.ext.schain', validBidRequests[0].schain); } // Attaching GDPR Consent Params if (bidderRequest.gdprConsent) { - utils.deepSetValue(openrtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - utils.deepSetValue(openrtbRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + deepSetValue(openrtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(openrtbRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } // CCPA if (bidderRequest.uspConsent) { - utils.deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); } const payloadString = JSON.stringify(openrtbRequest); @@ -96,15 +96,18 @@ export const spec = { creativeId: bid.crid, netRevenue: DEFAULT_NET_REVENUE, currency: DEFAULT_CURRENCY, + meta: { + adomain: bid.adomain + } }) }) } else { - utils.logInfo('yieldlift.interpretResponse :: no valid responses to interpret'); + logInfo('yieldlift.interpretResponse :: no valid responses to interpret'); } return bidResponses; }, getUserSyncs: function (syncOptions, serverResponses) { - utils.logInfo('yieldlift.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); + logInfo('yieldlift.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); let syncs = []; if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { @@ -112,7 +115,7 @@ export const spec = { } serverResponses.forEach(resp => { - const userSync = utils.deepAccess(resp, 'body.ext.usersync'); + const userSync = deepAccess(resp, 'body.ext.usersync'); if (userSync) { let syncDetails = []; Object.keys(userSync).forEach(key => { @@ -136,7 +139,7 @@ export const spec = { } } }); - utils.logInfo('yieldlift.getUserSyncs result=%o', syncs); + logInfo('yieldlift.getUserSyncs result=%o', syncs); return syncs; }, diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 36f93f60c9e..6642d1e9b83 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -1,9 +1,10 @@ -import * as utils from '../src/utils.js'; +import { isNumber, isStr, isInteger, isBoolean, isArray, isEmpty, isArrayOfNums, getWindowTop, parseQueryStringParameters, parseUrl, deepSetValue, deepAccess, logError } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import includes from 'core-js-pure/features/array/includes'; import find from 'core-js-pure/features/array/find.js'; +import { createEidsArray } from './userId/eids.js'; const BIDDER_CODE = 'yieldmo'; const CURRENCY = 'USD'; @@ -12,10 +13,10 @@ const NET_REVENUE = true; const BANNER_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; const VIDEO_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo'; const OUTSTREAM_VIDEO_PLAYER_URL = 'https://prebid-outstream.yieldmo.com/bundle.js'; -const OPENRTB_VIDEO_BIDPARAMS = ['placement', 'startdelay', 'skipafter', - 'protocols', 'api', 'playbackmethod', 'maxduration', 'minduration', 'pos']; +const OPENRTB_VIDEO_BIDPARAMS = ['mimes', 'startdelay', 'placement', 'startdelay', 'skipafter', 'protocols', 'api', + 'playbackmethod', 'maxduration', 'minduration', 'pos', 'skip', 'skippable']; const OPENRTB_VIDEO_SITEPARAMS = ['name', 'domain', 'cat', 'keywords']; -const LOCAL_WINDOW = utils.getWindowTop(); +const LOCAL_WINDOW = getWindowTop(); const DEFAULT_PLAYBACK_METHOD = 2; const DEFAULT_START_DELAY = 0; const VAST_TIMEOUT = 15000; @@ -46,29 +47,32 @@ export const spec = { buildRequests: function (bidRequests, bidderRequest) { const bannerBidRequests = bidRequests.filter(request => hasBannerMediaType(request)); const videoBidRequests = bidRequests.filter(request => hasVideoMediaType(request)); - let serverRequests = []; + const eids = getEids(bidRequests[0]) || []; if (bannerBidRequests.length > 0) { let serverRequest = { pbav: '$prebid.version$', p: [], page_url: bidderRequest.refererInfo.referer, bust: new Date().getTime().toString(), - pr: (LOCAL_WINDOW.document && LOCAL_WINDOW.document.referrer) || '', - scrd: LOCAL_WINDOW.devicePixelRatio || 0, dnt: getDNT(), description: getPageDescription(), - title: LOCAL_WINDOW.document.title || '', - w: LOCAL_WINDOW.innerWidth, - h: LOCAL_WINDOW.innerHeight, userConsent: JSON.stringify({ // case of undefined, stringify will remove param - gdprApplies: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', - cmp: utils.deepAccess(bidderRequest, 'gdprConsent.consentString') || '' + gdprApplies: deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', + cmp: deepAccess(bidderRequest, 'gdprConsent.consentString') || '' }), - us_privacy: utils.deepAccess(bidderRequest, 'uspConsent') || '' + us_privacy: deepAccess(bidderRequest, 'uspConsent') || '' }; + if (canAccessTopWindow()) { + serverRequest.pr = (LOCAL_WINDOW.document && LOCAL_WINDOW.document.referrer) || ''; + serverRequest.scrd = LOCAL_WINDOW.devicePixelRatio || 0; + serverRequest.title = LOCAL_WINDOW.document.title || ''; + serverRequest.w = LOCAL_WINDOW.innerWidth; + serverRequest.h = LOCAL_WINDOW.innerHeight; + } + const mtp = window.navigator.maxTouchPoints; if (mtp) { serverRequest.mtp = mtp; @@ -93,11 +97,17 @@ export const spec = { if (request.schain) { serverRequest.schain = JSON.stringify(request.schain); } + if (deepAccess(request, 'params.lr_env')) { + serverRequest.ats_envelope = request.params.lr_env; + } }); serverRequest.p = '[' + serverRequest.p.toString() + ']'; + if (eids.length) { + serverRequest.eids = JSON.stringify(eids); + }; // check if url exceeded max length - const url = `${BANNER_SERVER_ENDPOINT}?${utils.parseQueryStringParameters(serverRequest)}`; + const url = `${BANNER_SERVER_ENDPOINT}?${parseQueryStringParameters(serverRequest)}`; let extraCharacters = url.length - MAX_BANNER_REQUEST_URL_LENGTH; if (extraCharacters > 0) { for (let i = 0; i < BANNER_REQUEST_PROPERTIES_TO_REDUCE.length; i++) { @@ -118,6 +128,9 @@ export const spec = { if (videoBidRequests.length > 0) { const serverRequest = openRtbRequest(videoBidRequests, bidderRequest); + if (eids.length) { + serverRequest.user = { eids }; + }; serverRequests.push({ method: 'POST', url: VIDEO_SERVER_ENDPOINT, @@ -164,14 +177,14 @@ registerBidder(spec); * @param {BidRequest} bidRequest bid request */ function hasBannerMediaType(bidRequest) { - return !!utils.deepAccess(bidRequest, 'mediaTypes.banner'); + return !!deepAccess(bidRequest, 'mediaTypes.banner'); } /** * @param {BidRequest} bidRequest bid request */ function hasVideoMediaType(bidRequest) { - return !!utils.deepAccess(bidRequest, 'mediaTypes.video'); + return !!deepAccess(bidRequest, 'mediaTypes.video'); } /** @@ -179,6 +192,7 @@ function hasVideoMediaType(bidRequest) { * @param request bid request */ function addPlacement(request) { + const gpid = deepAccess(request, 'ortb2Imp.ext.data.pbadslot'); const placementInfo = { placement_id: request.adUnitCode, callback_id: request.bidId, @@ -193,6 +207,9 @@ function addPlacement(request) { placementInfo.bidFloor = bidfloor; } } + if (gpid) { + placementInfo.gpid = gpid; + } return JSON.stringify(placementInfo); } @@ -224,7 +241,7 @@ function createNewBannerBid(response) { * @param bidRequest server request */ function createNewVideoBid(response, bidRequest) { - const imp = find((utils.deepAccess(bidRequest, 'data.imp') || []), imp => imp.id === response.impid); + const imp = find((deepAccess(bidRequest, 'data.imp') || []), imp => imp.id === response.impid); let result = { requestId: imp.id, @@ -303,7 +320,7 @@ function getPageDescription() { * @returns an id if there is one, or undefined */ function getId(request, idType) { - return (typeof utils.deepAccess(request, 'userId') === 'object') ? request.userId[idType] : undefined; + return (typeof deepAccess(request, 'userId') === 'object') ? request.userId[idType] : undefined; } /** @@ -312,19 +329,25 @@ function getId(request, idType) { * @return Object OpenRTB request object */ function openRtbRequest(bidRequests, bidderRequest) { + const schain = bidRequests[0].schain; let openRtbRequest = { id: bidRequests[0].bidderRequestId, at: 1, imp: bidRequests.map(bidRequest => openRtbImpression(bidRequest)), site: openRtbSite(bidRequests[0], bidderRequest), - device: openRtbDevice(), + device: openRtbDevice(bidRequests[0]), badv: bidRequests[0].params.badv || [], bcat: bidRequests[0].params.bcat || [], ext: { prebid: '$prebid.version$', - } + }, + ats_envelope: bidRequests[0].params.lr_env, }; + if (schain) { + openRtbRequest.schain = schain; + } + populateOpenRtbGdpr(openRtbRequest, bidderRequest); return openRtbRequest; @@ -335,7 +358,7 @@ function openRtbRequest(bidRequests, bidderRequest) { * @return Object OpenRTB's 'imp' (impression) object */ function openRtbImpression(bidRequest) { - const videoReq = utils.deepAccess(bidRequest, 'mediaTypes.video'); + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); const size = extractPlayerSize(bidRequest); const imp = { id: bidRequest.bidId, @@ -347,23 +370,30 @@ function openRtbImpression(bidRequest) { video: { w: size[0], h: size[1], - mimes: videoReq.mimes, linearity: 1 } }; - const videoParams = utils.deepAccess(bidRequest, 'params.video'); + const mediaTypesParams = deepAccess(bidRequest, 'mediaTypes.video'); + Object.keys(mediaTypesParams) + .filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param)) + .forEach(param => imp.video[param] = mediaTypesParams[param]); + + const videoParams = deepAccess(bidRequest, 'params.video'); Object.keys(videoParams) .filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param)) .forEach(param => imp.video[param] = videoParams[param]); - if (videoParams.skippable) imp.video.skip = 1; - if (videoParams.placement !== 1) { - imp.video = { - ...imp.video, - startdelay: DEFAULT_START_DELAY, - playbackmethod: [ DEFAULT_PLAYBACK_METHOD ] - } + if (imp.video.skippable) { + imp.video.skip = 1; + delete imp.video.skippable; + } + if (imp.video.placement !== 1) { + imp.video.startdelay = DEFAULT_START_DELAY; + imp.video.playbackmethod = [ DEFAULT_PLAYBACK_METHOD ]; + } + if (gpid) { + imp.ext.gpid = gpid; } return imp; } @@ -383,10 +413,10 @@ function getBidFloor(bidRequest, mediaType) { * @return [number, number] || null Player's width and height, or undefined otherwise. */ function extractPlayerSize(bidRequest) { - const sizeArr = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); - if (utils.isArrayOfNums(sizeArr, 2)) { + const sizeArr = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + if (isArrayOfNums(sizeArr, 2)) { return sizeArr; - } else if (utils.isArray(sizeArr) && utils.isArrayOfNums(sizeArr[0], 2)) { + } else if (isArray(sizeArr) && isArrayOfNums(sizeArr[0], 2)) { return sizeArr[0]; } return null; @@ -400,8 +430,8 @@ function extractPlayerSize(bidRequest) { function openRtbSite(bidRequest, bidderRequest) { let result = {}; - const loc = utils.parseUrl(utils.deepAccess(bidderRequest, 'refererInfo.referer')); - if (!utils.isEmpty(loc)) { + const loc = parseUrl(deepAccess(bidderRequest, 'refererInfo.referer')); + if (!isEmpty(loc)) { result.page = `${loc.protocol}://${loc.hostname}${loc.pathname}`; } @@ -414,7 +444,7 @@ function openRtbSite(bidRequest, bidderRequest) { result.keywords = keywords.content; } - const siteParams = utils.deepAccess(bidRequest, 'params.site'); + const siteParams = deepAccess(bidRequest, 'params.site'); if (siteParams) { Object.keys(siteParams) .filter(param => includes(OPENRTB_VIDEO_SITEPARAMS, param)) @@ -426,11 +456,12 @@ function openRtbSite(bidRequest, bidderRequest) { /** * @return Object OpenRTB's 'device' object */ -function openRtbDevice() { - return { +function openRtbDevice(bidRequest) { + const deviceObj = { ua: navigator.userAgent, language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), }; + return deviceObj; } /** @@ -441,12 +472,12 @@ function openRtbDevice() { function populateOpenRtbGdpr(openRtbRequest, bidderRequest) { const gdpr = bidderRequest.gdprConsent; if (gdpr && 'gdprApplies' in gdpr) { - utils.deepSetValue(openRtbRequest, 'regs.ext.gdpr', gdpr.gdprApplies ? 1 : 0); - utils.deepSetValue(openRtbRequest, 'user.ext.consent', gdpr.consentString); + deepSetValue(openRtbRequest, 'regs.ext.gdpr', gdpr.gdprApplies ? 1 : 0); + deepSetValue(openRtbRequest, 'user.ext.consent', gdpr.consentString); } - const uspConsent = utils.deepAccess(bidderRequest, 'uspConsent'); + const uspConsent = deepAccess(bidderRequest, 'uspConsent'); if (uspConsent) { - utils.deepSetValue(openRtbRequest, 'regs.ext.us_privacy', uspConsent); + deepSetValue(openRtbRequest, 'regs.ext.us_privacy', uspConsent); } } @@ -476,58 +507,75 @@ function validateVideoParams(bid) { const isDefined = val => typeof val !== 'undefined'; const validate = (fieldPath, validateCb, errorCb, errorCbParam) => { - const value = utils.deepAccess(bid, fieldPath); - if (!validateCb(value)) { - errorCb(fieldPath, value, errorCbParam); + if (fieldPath.indexOf('video') === 0) { + const valueFieldPath = 'params.' + fieldPath; + const mediaFieldPath = 'mediaTypes.' + fieldPath; + const valueParams = deepAccess(bid, valueFieldPath); + const mediaTypesParams = deepAccess(bid, mediaFieldPath); + const hasValidValueParams = validateCb(valueParams); + const hasValidMediaTypesParams = validateCb(mediaTypesParams); + + if (hasValidValueParams) return valueParams; + else if (hasValidMediaTypesParams) return hasValidMediaTypesParams; + else { + if (!hasValidValueParams) errorCb(valueFieldPath, valueParams, errorCbParam); + else if (!hasValidMediaTypesParams) errorCb(mediaFieldPath, mediaTypesParams, errorCbParam); + } + return valueParams || mediaTypesParams; + } else { + const value = deepAccess(bid, fieldPath); + if (!validateCb(value)) { + errorCb(fieldPath, value, errorCbParam); + } + return value; } - return value; } try { - validate('params.placementId', val => !utils.isEmpty(val), paramRequired); + validate('video.context', val => !isEmpty(val), paramRequired); + + validate('params.placementId', val => !isEmpty(val), paramRequired); - validate('mediaTypes.video.playerSize', val => utils.isArrayOfNums(val, 2) || - (utils.isArray(val) && val.every(v => utils.isArrayOfNums(v, 2))), + validate('video.playerSize', val => isArrayOfNums(val, 2) || + (isArray(val) && val.every(v => isArrayOfNums(v, 2))), paramInvalid, 'array of 2 integers, ex: [640,480] or [[640,480]]'); - validate('mediaTypes.video.mimes', val => isDefined(val), paramRequired); - validate('mediaTypes.video.mimes', val => utils.isArray(val) && val.every(v => utils.isStr(v)), paramInvalid, + validate('video.mimes', val => isDefined(val), paramRequired); + validate('video.mimes', val => isArray(val) && val.every(v => isStr(v)), paramInvalid, 'array of strings, ex: ["video/mp4"]'); - validate('params.video', val => !utils.isEmpty(val), paramRequired); - - const placement = validate('params.video.placement', val => isDefined(val), paramRequired); - validate('params.video.placement', val => val >= 1 && val <= 5, paramInvalid); + const placement = validate('video.placement', val => isDefined(val), paramRequired); + validate('video.placement', val => val >= 1 && val <= 5, paramInvalid); if (placement === 1) { - validate('params.video.startdelay', val => isDefined(val), + validate('video.startdelay', val => isDefined(val), (field, v) => paramRequired(field, v, 'placement == 1')); - validate('params.video.startdelay', val => utils.isNumber(val), paramInvalid, 'number, ex: 5'); + validate('video.startdelay', val => isNumber(val), paramInvalid, 'number, ex: 5'); } - validate('params.video.protocols', val => isDefined(val), paramRequired); - validate('params.video.protocols', val => utils.isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)), + validate('video.protocols', val => isDefined(val), paramRequired); + validate('video.protocols', val => isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)), paramInvalid, 'array of numbers, ex: [2,3]'); - validate('params.video.api', val => isDefined(val), paramRequired); - validate('params.video.api', val => utils.isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)), + validate('video.api', val => isDefined(val), paramRequired); + validate('video.api', val => isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)), paramInvalid, 'array of numbers, ex: [2,3]'); - validate('params.video.playbackmethod', val => !isDefined(val) || utils.isArrayOfNums(val), paramInvalid, + validate('video.playbackmethod', val => !isDefined(val) || isArrayOfNums(val), paramInvalid, 'array of integers, ex: [2,6]'); - validate('params.video.maxduration', val => isDefined(val), paramRequired); - validate('params.video.maxduration', val => utils.isInteger(val), paramInvalid); - validate('params.video.minduration', val => !isDefined(val) || utils.isNumber(val), paramInvalid); - validate('params.video.skippable', val => !isDefined(val) || utils.isBoolean(val), paramInvalid); - validate('params.video.skipafter', val => !isDefined(val) || utils.isNumber(val), paramInvalid); - validate('params.video.pos', val => !isDefined(val) || utils.isNumber(val), paramInvalid); - validate('params.badv', val => !isDefined(val) || utils.isArray(val), paramInvalid, + validate('video.maxduration', val => isDefined(val), paramRequired); + validate('video.maxduration', val => isInteger(val), paramInvalid); + validate('video.minduration', val => !isDefined(val) || isNumber(val), paramInvalid); + validate('video.skippable', val => !isDefined(val) || isBoolean(val), paramInvalid); + validate('video.skipafter', val => !isDefined(val) || isNumber(val), paramInvalid); + validate('video.pos', val => !isDefined(val) || isNumber(val), paramInvalid); + validate('params.badv', val => !isDefined(val) || isArray(val), paramInvalid, 'array of strings, ex: ["ford.com","pepsi.com"]'); - validate('params.bcat', val => !isDefined(val) || utils.isArray(val), paramInvalid, + validate('params.bcat', val => !isDefined(val) || isArray(val), paramInvalid, 'array of strings, ex: ["IAB1-5","IAB1-6"]'); return true; } catch (e) { - utils.logError(e.message); + logError(e.message); return false; } } @@ -553,3 +601,29 @@ function shortcutProperty(extraCharacters, target, propertyName) { return charactersLeft; } + +/** + * Creates and returnes eids arr using createEidsArray from './userId/eids.js' module; + * @param {Object} openRtbRequest OpenRTB's request as a cource of userId. + * @return array of eids objects + */ +function getEids(bidRequest) { + if (deepAccess(bidRequest, 'userId')) { + return createEidsArray(bidRequest.userId) || []; + } +}; + +/** + * Check if top window can be accessed + * + * @return {boolean} true if can access top window otherwise false + */ +function canAccessTopWindow() { + try { + if (getWindowTop().location.href) { + return true; + } + } catch (error) { + return false; + } +} diff --git a/modules/yieldmoBidAdapter.md b/modules/yieldmoBidAdapter.md index 54be295a1a1..c98e2ab5c74 100644 --- a/modules/yieldmoBidAdapter.md +++ b/modules/yieldmoBidAdapter.md @@ -29,8 +29,9 @@ var adUnits = [{ // Banner adUnit bids: [{ bidder: 'yieldmo', params: { - placementId: '1779781193098233305', // string with at most 19 characters (may include numbers only) - bidFloor: .28 // optional param + placementId: '1779781193098233305', // string with at most 19 characters (may include numbers only) + bidFloor: .28, // optional param + lr_env: '***' // Optional. Live Ramp ATS envelope } }] }]; @@ -64,7 +65,8 @@ var adUnits = [{ // Video adUnit playbackmethod: [2,6], // required, array of integers skippable: true, // optional, boolean skipafter: 10 // optional, integer - } + }, + lr_env: '***' // Optional. Live Ramp ATS envelope } }] }]; @@ -91,8 +93,14 @@ var videoAdUnit = [{ protocols: [2, 3], // required, array of integers api: [2, 3], // required, array of integers playbackmethod: [1,2] // required, array of integers - } + }, + lr_env: '***' // Optional. Live Ramp ATS envelope } }] }]; ``` + +Please also note, that we support the following OpenRTB params: +'mimes', 'startdelay', 'placement', 'startdelay', 'skipafter', 'protocols', 'api', +'playbackmethod', 'maxduration', 'minduration', 'pos', 'skip', 'skippable'. +They can be specified in `mediaTypes.video` or in `bids[].params.video`. diff --git a/modules/yieldmoSyntheticInventoryModule.js b/modules/yieldmoSyntheticInventoryModule.js new file mode 100644 index 00000000000..bca778a7b43 --- /dev/null +++ b/modules/yieldmoSyntheticInventoryModule.js @@ -0,0 +1,46 @@ +import { config } from '../src/config.js'; +import { isGptPubadsDefined } from '../src/utils.js'; + +export const MODULE_NAME = 'Yieldmo Synthetic Inventory Module'; + +export function init(config) { + validateConfig(config); + + if (!isGptPubadsDefined()) { + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + } + + const googletag = window.googletag; + const containerName = 'ym_sim_container_' + config.placementId; + + googletag.cmd.push(() => { + if (window.document.body) { + googletagCmd(config, containerName, googletag); + } else { + window.document.addEventListener('DOMContentLoaded', () => googletagCmd(config, containerName, googletag)); + } + }); +} + +export function validateConfig(config) { + if (!('placementId' in config)) { + throw new Error(`${MODULE_NAME}: placementId required`); + } + if (!('adUnitPath' in config)) { + throw new Error(`${MODULE_NAME}: adUnitPath required`); + } +} + +function googletagCmd(config, containerName, googletag) { + const gamContainer = window.document.createElement('div'); + gamContainer.id = containerName; + window.document.body.appendChild(gamContainer); + googletag.defineSlot(config.adUnitPath, [1, 1], containerName) + .addService(googletag.pubads()) + .setTargeting('ym_sim_p_id', config.placementId); + googletag.enableServices(); + googletag.display(containerName); +} + +config.getConfig('yieldmo_synthetic_inventory', config => init(config.yieldmo_synthetic_inventory)); diff --git a/modules/yieldmoSyntheticInventoryModule.md b/modules/yieldmoSyntheticInventoryModule.md new file mode 100644 index 00000000000..dd6f0acf884 --- /dev/null +++ b/modules/yieldmoSyntheticInventoryModule.md @@ -0,0 +1,68 @@ +# Yieldmo Synthetic Inventory Module + +## Overview + +This module enables publishers to set up Yieldmo Synthetic Outstream ads on their pages. + +If publishers will enable this module and provide placementId and Google Ad Manager ad unit path, this module will create a placement on the page and inject Yieldmo SDK into this placement. Publisher will then need to get a placement id from their Yieldmo account manager (accounts email) and setup corresponding ad units on the GAM ad server. + +## Integration + +Build the Yieldmo Synthetic Inventory Module into the Prebid.js package with: + +``` +gulp build --modules=yieldmoSyntheticInventoryModule,... +``` + +## Module Configuration + +```js +pbjs.que.push(function() { + pbjs.setConfig({ + yieldmo_synthetic_inventory: { + placementId: '1234567890', + adUnitPath: '/1234567/ad_unit_name_used_in_gam' + } + }); +}); +``` + +### Configuration Parameters + +|Name |Scope |Description | Example| Type +| :------------ | :------------ | :------------ | :------------ | :------------ | +|placementId | required | Yieldmo placement ID | '1234567890' | string +|adUnitPath | required | Google Ad Manager ad unit path | '/6355419/ad_unit_name_used_in_gam' | string + +### How to get ad unit path + +Ad unit path follows the format /network-code/[parent-ad-unit-code/.../]ad-unit-code, where: + +- network-code is a unique identifier for the Ad Manager network the ad unit belongs to +- parent-ad-unit-code are the codes of all parent ad units (only applies to non-top level ad units) +- ad-unit-code is the code for the ad unit to be displayed + +Note that all ad unit codes included in the ad unit path must adhere to the [formatting rules](https://support.google.com/admanager/answer/1628457#ad-unit-codes) specified by Ad Manager. + +Another and probably the easiest way to get an ad unit path is to get it from the google ad manager ad unit document header generated tag: + +```js +googletag.defineSlot('/1234567/ad_unit_name_used_in_gam', [1, 1], 'ad-container-id').addService(googletag.pubads()); +``` + +### How to get Yieldmo placement id + +Please reach out to your Yieldmo account's person or email to support@yieldmo.com + +### Google Ad Manager setup + +Yieldmo Synthetic Inventory Module is designed to be used along with Google Ad Manager. GAM should be set as usual, but there are a few requirements: + +- Ad unit size should be 1x1 +- Creative should NOT be served into a SafeFrame and also should have 1x1 size +- Synthetic Inventory Universal Tag should be used as 3rd party creative code +### Synthetic Inventory Universal Tag + +```js +
+``` \ No newline at end of file diff --git a/modules/yieldoneAnalyticsAdapter.js b/modules/yieldoneAnalyticsAdapter.js index 542c0917708..cb13503365e 100644 --- a/modules/yieldoneAnalyticsAdapter.js +++ b/modules/yieldoneAnalyticsAdapter.js @@ -1,10 +1,10 @@ +import { isArray, deepClone } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import { targeting } from '../src/targeting.js'; import { auctionManager } from '../src/auctionManager.js'; -import * as utils from '../src/utils.js'; const ANALYTICS_CODE = 'yieldone'; const analyticsType = 'endpoint'; @@ -42,7 +42,7 @@ function makeAdUnitNameMap() { } function addAdUnitNameForArray(ar, map) { - if (utils.isArray(ar)) { + if (isArray(ar)) { ar.forEach((it) => { addAdUnitName(it, map) }); } } @@ -51,14 +51,14 @@ function addAdUnitName(params, map) { if (params.adUnitCode && map[params.adUnitCode]) { params.adUnitName = map[params.adUnitCode]; } - if (utils.isArray(params.adUnits)) { + if (isArray(params.adUnits)) { params.adUnits.forEach((adUnit) => { if (adUnit.code && map[adUnit.code]) { adUnit.name = map[adUnit.code]; } }); } - if (utils.isArray(params.adUnitCodes)) { + if (isArray(params.adUnitCodes)) { params.adUnitNames = params.adUnitCodes.map((code) => map[code]); } ['bids', 'bidderRequests', 'bidsReceived', 'noBids'].forEach((it) => { @@ -71,13 +71,13 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { track({eventType, args = {}}) { if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) { const reqBidderId = `${args.bidderCode}_${args.auctionId}`; - requestedBidders[reqBidderId] = utils.deepClone(args); + requestedBidders[reqBidderId] = deepClone(args); requestedBidders[reqBidderId].bids = []; args.bids.forEach((bid) => { requestedBids[`${bid.bidId}_${bid.auctionId}`] = bid; }); } - if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT && utils.isArray(args)) { + if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT && isArray(args)) { const eventsStorage = yieldoneAnalytics.eventsStorage; const reqBidders = {}; args.forEach((bid) => { diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index 574967db291..334de9eb3fa 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import {deepAccess, isEmpty, isStr, logWarn, parseSizesInput} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; @@ -11,6 +11,8 @@ const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/d const CMER_PLAYER_URL = 'https://an.cmertv.com/hb/renderer/cmertv-video-yone-prebid.min.js'; const VIEWABLE_PERCENTAGE_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/prebid-adformat-config.js'; +const DEFAULT_VIDEO_SIZE = {w: 640, h: 360}; + export const spec = { code: BIDDER_CODE, aliases: ['y1'], @@ -40,16 +42,30 @@ export const spec = { t: 'i' }; - const videoMediaType = utils.deepAccess(bidRequest, 'mediaTypes.video'); - if ((utils.isEmpty(bidRequest.mediaType) && utils.isEmpty(bidRequest.mediaTypes)) || - (bidRequest.mediaType === BANNER || (bidRequest.mediaTypes && bidRequest.mediaTypes[BANNER]))) { - const sizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; - payload.sz = utils.parseSizesInput(sizes).join(','); - } else if (bidRequest.mediaType === VIDEO || videoMediaType) { - const sizes = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') || bidRequest.sizes; - const size = utils.parseSizesInput(sizes)[0]; - payload.w = size.split('x')[0]; - payload.h = size.split('x')[1]; + const mediaType = getMediaType(bidRequest); + switch (mediaType) { + case BANNER: + payload.sz = getBannerSizes(bidRequest); + break; + case VIDEO: + const videoSize = getVideoSize(bidRequest); + payload.w = videoSize.w; + payload.h = videoSize.h; + break; + default: + break; + } + + // LiveRampID + const idlEnv = deepAccess(bidRequest, 'userId.idl_env'); + if (isStr(idlEnv) && !isEmpty(idlEnv)) { + payload.lr_env = idlEnv; + } + + // IMID + const imuid = deepAccess(bidRequest, 'userId.imuid'); + if (isStr(imuid) && !isEmpty(imuid)) { + payload.imuid = imuid; } return { @@ -82,7 +98,10 @@ export const spec = { currency: currency, netRevenue: netRevenue, ttl: config.getConfig('_bidderTimeout'), - referrer: referrer + referrer: referrer, + meta: { + advertiserDomains: response.adomain ? response.adomain : [] + }, }; if (response.adTag && renderId === 'ViewableRendering') { @@ -158,6 +177,106 @@ export const spec = { }, } +/** + * NOTE: server side does not yet support multiple formats. + * @param {Object} bidRequest - + * @param {boolean} [enabledOldFormat = true] - default: `true`. + * @return {string|null} - `"banner"` or `"video"` or `null`. + */ +function getMediaType(bidRequest, enabledOldFormat = true) { + let hasBannerType = Boolean(deepAccess(bidRequest, 'mediaTypes.banner')); + let hasVideoType = Boolean(deepAccess(bidRequest, 'mediaTypes.video')); + + if (enabledOldFormat) { + hasBannerType = hasBannerType || bidRequest.mediaType === BANNER || + (isEmpty(bidRequest.mediaTypes) && isEmpty(bidRequest.mediaType)); + hasVideoType = hasVideoType || bidRequest.mediaType === VIDEO; + } + + if (hasBannerType && hasVideoType) { + const playerParams = deepAccess(bidRequest, 'params.playerParams') + if (playerParams) { + return VIDEO; + } else { + return BANNER; + } + } else if (hasBannerType) { + return BANNER; + } else if (hasVideoType) { + return VIDEO; + } + + return null; +} + +/** + * NOTE: + * If `mediaTypes.banner` exists, then `mediaTypes.banner.sizes` must also exist. + * The reason for this is that Prebid.js will perform the verification and + * if `mediaTypes.banner.sizes` is inappropriate, it will delete the entire `mediaTypes.banner`. + * @param {Object} bidRequest - + * @param {Object} bidRequest.banner - + * @param {Array} bidRequest.banner.sizes - + * @param {boolean} [enabledOldFormat = true] - default: `true`. + * @return {string} - strings like `"300x250"` or `"300x250,728x90"`. + */ +function getBannerSizes(bidRequest, enabledOldFormat = true) { + let sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); + + if (enabledOldFormat) { + sizes = sizes || bidRequest.sizes; + } + + return parseSizesInput(sizes).join(','); +} + +/** + * @param {Object} bidRequest - + * @param {boolean} [enabledOldFormat = true] - default: `true`. + * @param {boolean} [enabledFlux = true] - default: `true`. + * @return {{w: number, h: number}} - + */ +function getVideoSize(bidRequest, enabledOldFormat = true, enabledFlux = true) { + /** + * @param {Array | Array>} sizes - + * @return {{w: number, h: number} | null} - + */ + const _getPlayerSize = (sizes) => { + let result = null; + + const size = parseSizesInput(sizes)[0]; + if (isEmpty(size)) { + return result; + } + + const splited = size.split('x'); + const sizeObj = {w: parseInt(splited[0], 10), h: parseInt(splited[1], 10)}; + const _isValidPlayerSize = !(isEmpty(sizeObj)) && (isFinite(sizeObj.w) && isFinite(sizeObj.h)); + if (!_isValidPlayerSize) { + return result; + } + + result = sizeObj; + return result; + } + + let playerSize = _getPlayerSize(deepAccess(bidRequest, 'mediaTypes.video.playerSize')); + + if (enabledOldFormat) { + playerSize = playerSize || _getPlayerSize(bidRequest.sizes); + } + + if (enabledFlux) { + // NOTE: `video.playerSize` in Flux is always [1,1]. + if (playerSize && (playerSize.w === 1 && playerSize.h === 1)) { + // NOTE: `params.playerSize` is a specific object to support `FLUX`. + playerSize = _getPlayerSize(deepAccess(bidRequest, 'params.playerSize')); + } + } + + return playerSize || DEFAULT_VIDEO_SIZE; +} + function newRenderer(response) { const renderer = Renderer.install({ id: response.uid, @@ -168,7 +287,7 @@ function newRenderer(response) { try { renderer.setRender(outstreamRender); } catch (err) { - utils.logWarn('Prebid Error calling setRender on newRenderer', err); + logWarn('Prebid Error calling setRender on newRenderer', err); } return renderer; @@ -190,7 +309,7 @@ function newCmerRenderer(response) { try { renderer.setRender(cmerRender); } catch (err) { - utils.logWarn('Prebid Error calling setRender on newRenderer', err); + logWarn('Prebid Error calling setRender on newRenderer', err); } return renderer; diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index 2ef2d251ace..080af9f3973 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -1,8 +1,8 @@ +import { getWindowLocation, generateUUID, parseUrl, buildUrl, logInfo, parseSizesInput, logError } 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 * as utils from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import strIncludes from 'core-js-pure/features/string/includes.js'; @@ -17,7 +17,7 @@ const events = { }; const localStoragePrefix = 'yuktamediaAnalytics_'; const utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; -const location = utils.getWindowLocation(); +const location = getWindowLocation(); const referer = getRefererInfo().referer; const _pageInfo = { userAgent: window.navigator.userAgent, @@ -25,13 +25,13 @@ const _pageInfo = { language: window.navigator.language, screenWidth: window.screen.width, screenHeight: window.screen.height, - pageViewId: utils.generateUUID(), + pageViewId: generateUUID(), host: location.host, path: location.pathname, search: location.search, hash: location.hash, referer: referer, - refererDomain: utils.parseUrl(referer).host, + refererDomain: parseUrl(referer).host, yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion, prebidVersion: $$PREBID_GLOBAL$$.version }; @@ -53,7 +53,7 @@ function isNavigatorSendBeaconSupported() { function updateSessionId() { if (isSessionIdTimeoutExpired()) { - let newSessionId = utils.generateUUID(); + let newSessionId = generateUUID(); storage.setDataInLocalStorage(localStoragePrefix.concat('session_id'), newSessionId); } initOptions.sessionId = getSessionId(); @@ -80,7 +80,7 @@ function isUtmTimeoutExpired() { function send(data, status) { data.initOptions = Object.assign(_pageInfo, initOptions); - const yuktamediaAnalyticsRequestUrl = utils.buildUrl({ + const yuktamediaAnalyticsRequestUrl = buildUrl({ protocol: 'https', hostname: 'analytics-prebid.yuktamedia.com', pathname: '/api/bids' @@ -97,13 +97,13 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin if (typeof args !== 'undefined') { switch (eventType) { case CONSTANTS.EVENTS.AUCTION_INIT: - utils.logInfo(localStoragePrefix + 'AUCTION_INIT:', JSON.stringify(args)); + logInfo(localStoragePrefix + 'AUCTION_INIT:', JSON.stringify(args)); if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { events.auctions[args.auctionId] = { bids: {} }; } break; case CONSTANTS.EVENTS.BID_REQUESTED: - utils.logInfo(localStoragePrefix + 'BID_REQUESTED:', JSON.stringify(args)); + logInfo(localStoragePrefix + 'BID_REQUESTED:', JSON.stringify(args)); if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { if (typeof events.auctions[args.auctionId] === 'undefined') { events.auctions[args.auctionId] = { bids: {} }; @@ -113,7 +113,7 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin events.auctions[args.auctionId]['bids'][bidRequest.bidId] = { bidder: bidRequest.bidder, adUnit: bidRequest.adUnitCode, - sizes: utils.parseSizesInput(bidRequest.sizes).toString(), + sizes: parseSizesInput(bidRequest.sizes).toString(), isBid: false, won: false, timeout: false, @@ -132,7 +132,7 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin } break; case CONSTANTS.EVENTS.BID_RESPONSE: - utils.logInfo(localStoragePrefix + 'BID_RESPONSE:', JSON.stringify(args)); + logInfo(localStoragePrefix + 'BID_RESPONSE:', JSON.stringify(args)); if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { if (typeof events.auctions[args.auctionId] === 'undefined') { events.auctions[args.auctionId] = { bids: {} }; @@ -162,7 +162,7 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin } break; case CONSTANTS.EVENTS.NO_BID: - utils.logInfo(localStoragePrefix + 'NO_BID:', JSON.stringify(args)); + logInfo(localStoragePrefix + 'NO_BID:', JSON.stringify(args)); if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { if (typeof events.auctions[args.auctionId] === 'undefined') { events.auctions[args.auctionId] = { bids: {} }; @@ -173,7 +173,7 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin } break; case CONSTANTS.EVENTS.BID_WON: - utils.logInfo(localStoragePrefix + 'BID_WON:', JSON.stringify(args)); + logInfo(localStoragePrefix + 'BID_WON:', JSON.stringify(args)); if (typeof initOptions.enableSession !== 'undefined' && initOptions.enableSession) { updateSessionId(); } @@ -189,7 +189,7 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin } break; case CONSTANTS.EVENTS.BID_TIMEOUT: - utils.logInfo(localStoragePrefix + 'BID_TIMEOUT:', JSON.stringify(args)); + logInfo(localStoragePrefix + 'BID_TIMEOUT:', JSON.stringify(args)); if (args.length) { args.forEach(timeout => { if (typeof timeout !== 'undefined' && typeof timeout.auctionId !== 'undefined' && timeout.auctionId.length) { @@ -205,7 +205,7 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin } break; case CONSTANTS.EVENTS.AUCTION_END: - utils.logInfo(localStoragePrefix + 'AUCTION_END:', JSON.stringify(args)); + logInfo(localStoragePrefix + 'AUCTION_END:', JSON.stringify(args)); if (typeof initOptions.enableSession !== 'undefined' && initOptions.enableSession) { updateSessionId(); } @@ -249,7 +249,7 @@ yuktamediaAnalyticsAdapter.originEnableAnalytics = yuktamediaAnalyticsAdapter.en yuktamediaAnalyticsAdapter.enableAnalytics = function (config) { if (config && config.options) { if (typeof config.options.pubId === 'undefined' || typeof config.options.pubKey === 'undefined') { - utils.logError('Need pubId and pubKey to log auction results. Please contact a YuktaMedia representative if you do not know your pubId and pubKey.'); + logError('Need pubId and pubKey to log auction results. Please contact a YuktaMedia representative if you do not know your pubId and pubKey.'); return; } } diff --git a/modules/zedoBidAdapter.js b/modules/zedoBidAdapter.js deleted file mode 100644 index e75b9c82065..00000000000 --- a/modules/zedoBidAdapter.js +++ /dev/null @@ -1,342 +0,0 @@ -import * as utils 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'; -import { Renderer } from '../src/Renderer.js'; -import { getRefererInfo } from '../src/refererDetection.js'; - -const BIDDER_CODE = 'zedo'; -const SECURE_URL = 'https://saxp.zedo.com/asw/fmh.json'; -const DIM_TYPE = { - '7': 'display', - '9': 'display', - '14': 'display', - '70': 'SBR', - '83': 'CurtainRaiser', - '85': 'Inarticle', - '86': 'pswipeup', - '88': 'Inview', - '100': 'display', - '101': 'display', - '102': 'display', - '103': 'display' - // '85': 'pre-mid-post-roll', -}; -const SECURE_EVENT_PIXEL_URL = 'tt1.zedo.com/log/p.gif'; - -export const spec = { - code: BIDDER_CODE, - aliases: [], - supportedMediaTypes: [BANNER, VIDEO], - - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function (bid) { - return !!(bid.params && bid.params.channelCode && bid.params.dimId); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function (bidRequests, bidderRequest) { - let data = { - placements: [] - }; - bidRequests.map(bidRequest => { - let channelCode = parseInt(bidRequest.params.channelCode); - let network = parseInt(channelCode / 1000000); - let channel = channelCode % 1000000; - let dim = getSizes(bidRequest.sizes); - let placement = { - id: bidRequest.bidId, - network: network, - channel: channel, - publisher: bidRequest.params.pubId ? bidRequest.params.pubId : 0, - width: dim[0], - height: dim[1], - dimension: bidRequest.params.dimId, - version: '$prebid.version$', - keyword: '', - transactionId: bidRequest.transactionId - } - if (bidderRequest && bidderRequest.gdprConsent) { - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { - data.gdpr = Number(bidderRequest.gdprConsent.gdprApplies); - } - data.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - // Add CCPA consent string - if (bidderRequest && bidderRequest.uspConsent) { - data.usp = bidderRequest.uspConsent; - } - - let dimType = DIM_TYPE[String(bidRequest.params.dimId)] - if (dimType) { - placement['renderers'] = [{ - 'name': dimType - }] - } else { // default to display - placement['renderers'] = [{ - 'name': 'display' - }] - } - data['placements'].push(placement); - }); - // adding schain object - if (bidRequests[0].schain) { - data['supplyChain'] = getSupplyChain(bidRequests[0].schain); - } - return { - method: 'GET', - url: SECURE_URL, - data: 'g=' + JSON.stringify(data) - } - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, request) { - serverResponse = serverResponse.body; - const bids = []; - if (!serverResponse || serverResponse.error) { - let errorMessage = `in response for ${request.bidderCode} adapter`; - if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } - utils.logError(errorMessage); - return bids; - } - - if (serverResponse.ad) { - serverResponse.ad.forEach(ad => { - const creativeBid = getCreative(ad); - if (creativeBid) { - if (parseInt(creativeBid.cpm) !== 0) { - const bid = newBid(ad, creativeBid, request); - bid.mediaType = parseMediaType(creativeBid); - bids.push(bid); - } - } - }); - } - return bids; - }, - - getUserSyncs: function (syncOptions, responses, gdprConsent) { - if (syncOptions.iframeEnabled) { - let url = 'https://tt3.zedo.com/rs/us/fcs.html'; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - // add 'gdpr' only if 'gdprApplies' is defined - if (typeof gdprConsent.gdprApplies === 'boolean') { - url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - url += `?gdpr_consent=${gdprConsent.consentString}`; - } - } - return [{ - type: 'iframe', - url: url - }]; - } - }, - - onTimeout: function (timeoutData) { - try { - logEvent('117', timeoutData); - } catch (e) { - utils.logError(e); - } - }, - - onBidWon: function (bid) { - try { - logEvent('116', [bid]); - } catch (e) { - utils.logError(e); - } - } - -}; - -function getSupplyChain (supplyChain) { - return { - complete: supplyChain.complete, - nodes: supplyChain.nodes - } -}; - -function getCreative(ad) { - return ad && ad.creatives && ad.creatives.length && find(ad.creatives, creative => creative.adId); -}; -/** - * Unpack the Server's Bid into a Prebid-compatible one. - * @param serverBid - * @param rtbBid - * @param bidderRequest - * @return Bid - */ -function newBid(serverBid, creativeBid, bidderRequest) { - const bid = { - requestId: serverBid.slotId, - creativeId: creativeBid.adId, - network: serverBid.network, - adType: creativeBid.creativeDetails.type, - dealId: 99999999, - currency: 'USD', - netRevenue: true, - ttl: 300 - }; - - if (creativeBid.creativeDetails.type === 'VAST') { - Object.assign(bid, { - width: creativeBid.width, - height: creativeBid.height, - vastXml: creativeBid.creativeDetails.adContent, - cpm: parseInt(creativeBid.bidCpm) / 1000000, - ttl: 3600 - }); - const rendererOptions = utils.deepAccess( - bidderRequest, - 'renderer.options' - ); - let rendererUrl = 'https://ss3.zedo.com/gecko/beta/fmpbgt.min.js'; - Object.assign(bid, { - adResponse: serverBid, - renderer: getRenderer(bid.adUnitCode, serverBid.slotId, rendererUrl, rendererOptions) - }); - } else { - Object.assign(bid, { - width: creativeBid.width, - height: creativeBid.height, - cpm: parseInt(creativeBid.bidCpm) / 1000000, - ad: creativeBid.creativeDetails.adContent, - }); - } - - return bid; -} -/* Turn bid request sizes into compatible format */ -function getSizes(requestSizes) { - let width = 0; - let height = 0; - if (utils.isArray(requestSizes) && requestSizes.length === 2 && - !utils.isArray(requestSizes[0])) { - width = parseInt(requestSizes[0], 10); - height = parseInt(requestSizes[1], 10); - } else if (typeof requestSizes === 'object') { - for (let i = 0; i < requestSizes.length; i++) { - let size = requestSizes[i]; - width = parseInt(size[0], 10); - height = parseInt(size[1], 10); - break; - } - } - return [width, height]; -} - -function getRenderer(adUnitCode, rendererId, rendererUrl, rendererOptions = {}) { - const renderer = Renderer.install({ - id: rendererId, - url: rendererUrl, - config: rendererOptions, - loaded: false, - }); - - try { - renderer.setRender(videoRenderer); - } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); - } - - renderer.setEventHandlers({ - impression: () => utils.logMessage('ZEDO video impression'), - loaded: () => utils.logMessage('ZEDO video loaded'), - ended: () => { - utils.logMessage('ZEDO renderer video ended'); - document.querySelector(`#${adUnitCode}`).style.display = 'none'; - } - }); - return renderer; -} - -function videoRenderer(bid) { - // push to render queue - const refererInfo = getRefererInfo(); - let referrer = ''; - if (refererInfo) { - referrer = refererInfo.referer; - } - bid.renderer.push(() => { - let channelCode = utils.deepAccess(bid, 'params.0.channelCode') || 0; - let dimId = utils.deepAccess(bid, 'params.0.dimId') || 0; - let publisher = utils.deepAccess(bid, 'params.0.pubId') || 0; - let options = utils.deepAccess(bid, 'params.0.options') || {}; - let channel = (channelCode > 0) ? (channelCode - (bid.network * 1000000)) : 0; - - var rndr = new window.ZdPBTag(bid.adUnitCode, bid.network, bid.width, bid.height, bid.adType, bid.vastXml, channel, dimId, - (encodeURI(referrer) || ''), options); - rndr.renderAd(publisher); - }); -} - -function parseMediaType(creativeBid) { - const adType = creativeBid.creativeDetails.type; - if (adType === 'VAST') { - return VIDEO; - } else { - return BANNER; - } -} - -function logEvent(eid, data) { - let getParams = { - protocol: 'https', - hostname: SECURE_EVENT_PIXEL_URL, - search: getLoggingData(eid, data) - }; - let eventUrl = utils.buildUrl(getParams).replace(/&/g, ';'); - utils.triggerPixel(eventUrl); -} - -function getLoggingData(eid, data) { - data = (utils.isArray(data) && data) || []; - - let params = {}; - let channel, network, dim, publisher, adunitCode, timeToRespond, cpm; - data.map((adunit) => { - adunitCode = adunit.adUnitCode; - channel = utils.deepAccess(adunit, 'params.0.channelCode') || 0; - network = channel > 0 ? parseInt(channel / 1000000) : 0; - dim = utils.deepAccess(adunit, 'params.0.dimId') * 256 || 0; - publisher = utils.deepAccess(adunit, 'params.0.pubId') || 0; - timeToRespond = adunit.timeout ? adunit.timeout : adunit.timeToRespond; - cpm = adunit.cpm; - }); - let referrer = ''; - const refererInfo = getRefererInfo(); - if (refererInfo) { - referrer = refererInfo.referer; - } - params.n = network; - params.c = channel; - params.s = publisher; - params.x = dim; - params.ai = encodeURI('Prebid^zedo^' + adunitCode + '^' + cpm + '^' + timeToRespond); - params.pu = encodeURI(referrer) || ''; - params.eid = eid; - params.e = 'e'; - params.z = Math.random(); - - return params; -} - -registerBidder(spec); diff --git a/modules/zemantaBidAdapter.js b/modules/zemantaBidAdapter.js deleted file mode 100644 index b2c00b69c24..00000000000 --- a/modules/zemantaBidAdapter.js +++ /dev/null @@ -1,281 +0,0 @@ -// jshint esversion: 6, es3: false, node: true -'use strict'; - -import { - registerBidder -} from '../src/adapters/bidderFactory.js'; -import { NATIVE, BANNER } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; - -const BIDDER_CODE = 'zemanta'; -const GVLID = 164; -const CURRENCY = 'USD'; -const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; -const NATIVE_PARAMS = { - title: { id: 0, name: 'title' }, - icon: { id: 2, type: 1, name: 'img' }, - image: { id: 3, type: 3, name: 'img' }, - sponsoredBy: { id: 5, name: 'data', type: 1 }, - body: { id: 4, name: 'data', type: 2 }, - cta: { id: 1, type: 12, name: 'data' } -}; - -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - aliases: [ - { code: 'outbrain', gvlid: GVLID } - ], - supportedMediaTypes: [ NATIVE, BANNER ], - isBidRequestValid: (bid) => { - return ( - (!!config.getConfig('zemanta.bidderUrl') || !!config.getConfig('outbrain.bidderUrl')) && - !!utils.deepAccess(bid, 'params.publisher.id') && - !!(bid.nativeParams || bid.sizes) - ); - }, - buildRequests: (validBidRequests, bidderRequest) => { - const page = bidderRequest.refererInfo.referer; - const ua = navigator.userAgent; - const test = setOnAny(validBidRequests, 'params.test'); - const publisher = setOnAny(validBidRequests, 'params.publisher'); - const bcat = setOnAny(validBidRequests, 'params.bcat'); - const badv = setOnAny(validBidRequests, 'params.badv'); - const cur = CURRENCY; - const endpointUrl = config.getConfig('zemanta.bidderUrl') || config.getConfig('outbrain.bidderUrl'); - const timeout = bidderRequest.timeout; - - const imps = validBidRequests.map((bid, id) => { - bid.netRevenue = 'net'; - const imp = { - id: id + 1 + '' - } - - if (bid.params.tagid) { - imp.tagid = bid.params.tagid - } - - if (bid.nativeParams) { - imp.native = { - request: JSON.stringify({ - assets: getNativeAssets(bid) - }) - } - } else { - imp.banner = { - format: transformSizes(bid.sizes) - } - } - - return imp; - }); - - const request = { - id: bidderRequest.auctionId, - site: { page, publisher }, - device: { ua }, - source: { fd: 1 }, - cur: [cur], - tmax: timeout, - imp: imps, - bcat: bcat, - badv: badv, - }; - - if (test) { - request.is_debug = !!test; - request.test = 1; - } - - if (utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { - utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString) - utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1) - } - if (bidderRequest.uspConsent) { - utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent) - } - if (config.getConfig('coppa') === true) { - utils.deepSetValue(request, 'regs.coppa', config.getConfig('coppa') & 1) - } - - return { - method: 'POST', - url: endpointUrl, - data: JSON.stringify(request), - bids: validBidRequests - }; - }, - interpretResponse: (serverResponse, { bids }) => { - if (!serverResponse.body) { - return []; - } - const { seatbid, cur } = serverResponse.body; - - const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { - result[bid.impid - 1] = bid; - return result; - }, []); - - return bids.map((bid, id) => { - const bidResponse = bidResponses[id]; - if (bidResponse) { - const type = bid.nativeParams ? NATIVE : BANNER; - const bidObject = { - requestId: bid.bidId, - cpm: bidResponse.price, - creativeId: bidResponse.crid, - ttl: 360, - netRevenue: bid.netRevenue === 'net', - currency: cur, - mediaType: type, - nurl: bidResponse.nurl, - }; - if (type === NATIVE) { - bidObject.native = parseNative(bidResponse); - } else { - bidObject.ad = bidResponse.adm; - bidObject.width = bidResponse.w; - bidObject.height = bidResponse.h; - } - bidObject.meta = {}; - if (bidResponse.adomain && bidResponse.adomain.length > 0) { - bidObject.meta.advertiserDomains = bidResponse.adomain; - } - return bidObject; - } - }).filter(Boolean); - }, - getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { - const syncs = []; - let syncUrl = config.getConfig('zemanta.usersyncUrl') || config.getConfig('outbrain.usersyncUrl'); - if (syncOptions.pixelEnabled && syncUrl) { - if (gdprConsent) { - syncUrl += '&gdpr=' + (gdprConsent.gdprApplies & 1); - syncUrl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); - } - if (uspConsent) { - syncUrl += '&us_privacy=' + encodeURIComponent(uspConsent); - } - - syncs.push({ - type: 'image', - url: syncUrl - }); - } - return syncs; - }, - onBidWon: (bid) => { - // for native requests we put the nurl as an imp tracker, otherwise if the auction takes place on prebid server - // the server JS adapter puts the nurl in the adm as a tracking pixel and removes the attribute - if (bid.nurl) { - ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) - } - } -}; - -registerBidder(spec); - -function parseNative(bid) { - const { assets, link, eventtrackers } = JSON.parse(bid.adm); - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || undefined - }; - assets.forEach(asset => { - const kind = NATIVE_ASSET_IDS[asset.id]; - const content = kind && asset[NATIVE_PARAMS[kind].name]; - if (content) { - result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; - } - }); - if (eventtrackers) { - result.impressionTrackers = []; - eventtrackers.forEach(tracker => { - if (tracker.event !== 1) return; - switch (tracker.method) { - case 1: // img - result.impressionTrackers.push(tracker.url); - break; - case 2: // js - result.javascriptTrackers = ``; - break; - } - }); - } - return result; -} - -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = utils.deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - -function flatten(arr) { - return [].concat(...arr); -} - -function getNativeAssets(bid) { - return utils._map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - const asset = { - required: bidParams.required & 1, - }; - if (props) { - asset.id = props.id; - let wmin, hmin, w, h; - let aRatios = bidParams.aspect_ratios; - - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - wmin = aRatios.min_width || 0; - hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; - } - - if (bidParams.sizes) { - const sizes = flatten(bidParams.sizes); - w = sizes[0]; - h = sizes[1]; - } - - asset[props.name] = { - len: bidParams.len, - type: props.type, - wmin, - hmin, - w, - h - }; - - return asset; - } - }).filter(Boolean); -} - -/* Turn bid request sizes into ut-compatible format */ -function transformSizes(requestSizes) { - if (!utils.isArray(requestSizes)) { - return []; - } - - if (requestSizes.length === 2 && !utils.isArray(requestSizes[0])) { - return [{ - w: parseInt(requestSizes[0], 10), - h: parseInt(requestSizes[1], 10) - }]; - } else if (utils.isArray(requestSizes[0])) { - return requestSizes.map(item => - ({ - w: parseInt(item[0], 10), - h: parseInt(item[1], 10) - }) - ); - } - - return []; -} diff --git a/modules/zeotapIdPlusIdSystem.js b/modules/zeotapIdPlusIdSystem.js index 8f26cc827d6..5cb3a836cd8 100644 --- a/modules/zeotapIdPlusIdSystem.js +++ b/modules/zeotapIdPlusIdSystem.js @@ -4,7 +4,7 @@ * @module modules/zeotapIdPlusIdSystem * @requires module:modules/userId */ -import * as utils from '../src/utils.js' +import { isStr, isPlainObject } from '../src/utils.js'; import {submodule} from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -45,7 +45,7 @@ export const zeotapIdPlusSubmodule = { * @return { Object | string | undefined } */ decode(value) { - const id = value ? utils.isStr(value) ? value : utils.isPlainObject(value) ? value.id : undefined : undefined; + const id = value ? isStr(value) ? value : isPlainObject(value) ? value.id : undefined : undefined; return id ? { 'IDP': JSON.parse(atob(id)) } : undefined; diff --git a/modules/zetaBidAdapter.js b/modules/zetaBidAdapter.js index ee5c854df97..27650888677 100644 --- a/modules/zetaBidAdapter.js +++ b/modules/zetaBidAdapter.js @@ -1,9 +1,10 @@ -import * as utils from '../src/utils.js'; +import { logWarn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; const BIDDER_CODE = 'zeta_global'; +const PREBID_DEFINER_ID = '44253' const ENDPOINT_URL = 'https://prebid.rfihub.com/prebid'; -const USER_SYNC_URL = 'https://p.rfihub.com/cm?pub=42770&in=1'; +const USER_SYNC_URL = 'https://p.rfihub.com/cm?in=1&pub='; const DEFAULT_CUR = 'USD'; const TTL = 200; const NET_REV = true; @@ -23,30 +24,30 @@ export const spec = { if (!(bid && bid.bidId && bid.params)) { - utils.logWarn('Invalid bid request - missing required bid data'); + logWarn('Invalid bid request - missing required bid data'); return false; } if (!(bid.params.user && bid.params.user.buyeruid)) { - utils.logWarn('Invalid bid request - missing required user data'); + logWarn('Invalid bid request - missing required user data'); return false; } if (!(bid.params.device && bid.params.device.ip)) { - utils.logWarn('Invalid bid request - missing required device data'); + logWarn('Invalid bid request - missing required device data'); return false; } if (!(bid.params.device.geo && bid.params.device.geo.country)) { - utils.logWarn('Invalid bid request - missing required geo data'); + logWarn('Invalid bid request - missing required geo data'); return false; } if (!bid.params.definerId) { - utils.logWarn('Invalid bid request - missing required definer data'); + logWarn('Invalid bid request - missing required definer data'); return false; } @@ -87,10 +88,12 @@ export const spec = { badv: params.badv, bapp: params.bapp, source: params.source ? params.source : {}, + regs: params.regs ? params.regs : {}, ext: params.ext ? params.ext : {} }; payload.device.ua = navigator.userAgent; + payload.device.ip = navigator.ip; payload.site.page = bidderRequest.refererInfo.referer; payload.site.mobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0; payload.ext.definerId = params.definerId; @@ -99,20 +102,18 @@ export const spec = { payload.test = params.test; } if (request.gdprConsent) { - payload.regs = { - ext: { - gdpr: request.gdprConsent.gdprApplies === true ? 1 : 0 - } - }; + payload.regs.ext = Object.assign( + payload.regs.ext, + {gdpr: request.gdprConsent.gdprApplies === true ? 1 : 0} + ); } if (request.gdprConsent && request.gdprConsent.gdprApplies) { - payload.user = { - ext: { - consent: request.gdprConsent.consentString - } - }; + payload.user.ext = Object.assign( + payload.user.ext, + {consent: request.gdprConsent.consentString} + ); } - const postUrl = params.definerId !== '0' ? ENDPOINT_URL.concat('/', params.definerId) : ENDPOINT_URL; + const postUrl = params.definerId !== PREBID_DEFINER_ID ? ENDPOINT_URL.concat('/', params.definerId) : ENDPOINT_URL; return { method: 'POST', url: postUrl, @@ -149,20 +150,24 @@ export const spec = { }, /** - * 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 The GDPR consent parameters - * @param uspConsent The USP consent parameters - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + * 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 definerId The calling entity's definer id + * @param gdprConsent The GDPR consent parameters + * @param uspConsent The USP consent parameters + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses, definerId, gdprConsent, uspConsent) { const syncs = []; + if (definerId === '' || definerId === null) { + definerId = PREBID_DEFINER_ID; + } if (syncOptions.iframeEnabled) { syncs.push({ type: 'iframe', - url: USER_SYNC_URL + url: USER_SYNC_URL.concat(definerId) }); } return syncs; diff --git a/modules/zetaBidAdapter.md b/modules/zetaBidAdapter.md index 89a9767d29a..e0f7271a4f1 100644 --- a/modules/zetaBidAdapter.md +++ b/modules/zetaBidAdapter.md @@ -35,7 +35,7 @@ Module that connects to Zeta's demand sources country: 'USA' } }, - definerId: '0', + definerId: '44253', test: 1 } } diff --git a/modules/zetaSspBidAdapter.js b/modules/zetaSspBidAdapter.js deleted file mode 100644 index 8f4d8995800..00000000000 --- a/modules/zetaSspBidAdapter.js +++ /dev/null @@ -1,159 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; - -const BIDDER_CODE = 'zeta_global_ssp'; -const ENDPOINT_URL = 'https:/ssp.disqus.com/bid'; -const USER_SYNC_URL = 'https:/ssp.disqus.com/match'; -const DEFAULT_CUR = 'USD'; -const TTL = 200; -const NET_REV = true; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - - /** - * 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) { - // check for all required bid fields - if (!(bid && - bid.bidId && - bid.params)) { - utils.logWarn('Invalid bid request - missing required bid data'); - return false; - } - return true; - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {Bids[]} validBidRequests - an array of bidRequest objects - * @param {BidderRequest} bidderRequest - master bidRequest object - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function (validBidRequests, bidderRequest) { - const secure = 1; // treat all requests as secure - const request = validBidRequests[0]; - const params = request.params; - let impData = { - id: request.bidId, - secure: secure, - banner: buildBanner(request) - }; - const fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; - let payload = { - id: bidderRequest.auctionId, - cur: [DEFAULT_CUR], - imp: [impData], - site: params.site ? params.site : {}, - device: fpd.device ? fpd.device : {}, - user: params.user ? params.user : {}, - app: params.app ? params.app : {}, - ext: { - tags: params.tags ? params.tags : {}, - sid: params.sid ? params.sid : {} - } - }; - - payload.device.ua = navigator.userAgent; - payload.site.page = bidderRequest.refererInfo.referer; - payload.site.mobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0; - - if (params.test) { - payload.test = params.test; - } - if (request.gdprConsent) { - payload.regs = { - ext: { - gdpr: request.gdprConsent.gdprApplies === true ? 1 : 0 - } - }; - } - if (request.gdprConsent && request.gdprConsent.gdprApplies) { - payload.user = { - ext: { - consent: request.gdprConsent.consentString - } - }; - } - return { - method: 'POST', - url: ENDPOINT_URL, - data: JSON.stringify(payload), - }; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @param bidRequest The payload from the server's response. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, bidRequest) { - let bidResponse = []; - if (Object.keys(serverResponse.body).length !== 0) { - let zetaResponse = serverResponse.body; - let zetaBid = zetaResponse.seatbid[0].bid[0]; - let bid = { - requestId: zetaBid.impid, - cpm: zetaBid.price, - currency: zetaResponse.cur, - width: zetaBid.w, - height: zetaBid.h, - ad: zetaBid.adm, - ttl: TTL, - creativeId: zetaBid.crid, - netRevenue: NET_REV, - }; - if (zetaBid.adomain && zetaBid.adomain.length) { - bid.meta = {}; - bid.meta.advertiserDomains = zetaBid.adomain; - } - bidResponse.push(bid); - } - return bidResponse; - }, - - /** - * 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 The GDPR consent parameters - * @param uspConsent The USP consent parameters - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { - const syncs = []; - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: USER_SYNC_URL - }); - } - return syncs; - } -} - -function buildBanner(request) { - let sizes = request.sizes; - if (request.mediaTypes && - request.mediaTypes.banner && - request.mediaTypes.banner.sizes) { - sizes = request.mediaTypes.banner.sizes; - } - return { - w: sizes[0][0], - h: sizes[0][1] - }; -} - -registerBidder(spec); diff --git a/modules/zetaSspBidAdapter.md b/modules/zetaSspBidAdapter.md index d2950bce6b9..00d8663586c 100644 --- a/modules/zetaSspBidAdapter.md +++ b/modules/zetaSspBidAdapter.md @@ -10,7 +10,7 @@ Maintainer: miakovlev@zetaglobal.com Module that connects to Zeta's SSP -# Test Parameters +# Banner Ad Unit: For Publishers ``` var adUnits = [ { @@ -40,3 +40,35 @@ Module that connects to Zeta's SSP } ]; ``` + +# Video Ad Unit: For Publishers +``` + var adUnits = [ + { + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + } + }, + bids: [ + { + bidder: 'zeta_global_ssp', + bidId: 12345, + params: { + placement: 12345, + user: { + uid: 12345, + buyeruid: 12345 + }, + tags: { + someTag: 123, + sid: 'publisherId' + }, + test: 1 + } + } + ] + } + ]; +``` diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js new file mode 100644 index 00000000000..906e6e19cc2 --- /dev/null +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -0,0 +1,97 @@ +import { logInfo, logError } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; + +import adapter from '../src/AnalyticsAdapter.js'; + +const ZETA_GVL_ID = 833; +const ADAPTER_CODE = 'zeta_global_ssp'; +const BASE_URL = 'https://ssp.disqus.com/prebid/event'; +const LOG_PREFIX = 'ZetaGlobalSsp-Analytics: '; + +/// /////////// VARIABLES //////////////////////////////////// + +let publisherId; // int + +/// /////////// HELPER FUNCTIONS ///////////////////////////// + +function sendEvent(eventType, event) { + ajax( + BASE_URL + '/' + eventType, + null, + JSON.stringify(event) + ); +} + +/// /////////// ADAPTER EVENT HANDLER FUNCTIONS ////////////// + +function adRenderSucceededHandler(args) { + let eventType = CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED + logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); + + sendEvent(eventType, args); +} + +function auctionEndHandler(args) { + let eventType = CONSTANTS.EVENTS.AUCTION_END; + logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); + + sendEvent(eventType, args); +} + +/// /////////// ADAPTER DEFINITION /////////////////////////// + +let baseAdapter = adapter({ analyticsType: 'endpoint' }); +let zetaAdapter = Object.assign({}, baseAdapter, { + + enableAnalytics(config = {}) { + let error = false; + + if (typeof config.options === 'object') { + if (config.options.sid) { + publisherId = Number(config.options.sid); + } + } else { + logError(LOG_PREFIX + 'Config not found'); + error = true; + } + + if (!publisherId) { + logError(LOG_PREFIX + 'Missing sid (publisher id)'); + error = true; + } + + if (error) { + logError(LOG_PREFIX + 'Analytics is disabled due to error(s)'); + } else { + baseAdapter.enableAnalytics.call(this, config); + } + }, + + disableAnalytics() { + publisherId = undefined; + baseAdapter.disableAnalytics.apply(this, arguments); + }, + + track({ eventType, args }) { + switch (eventType) { + case CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED: + adRenderSucceededHandler(args); + break; + case CONSTANTS.EVENTS.AUCTION_END: + auctionEndHandler(args); + break; + } + } +}); + +/// /////////// ADAPTER REGISTRATION ///////////////////////// + +adapterManager.registerAnalyticsAdapter({ + adapter: zetaAdapter, + code: ADAPTER_CODE, + gvlid: ZETA_GVL_ID +}); + +export default zetaAdapter; diff --git a/modules/zeta_global_sspAnalyticsAdapter.md b/modules/zeta_global_sspAnalyticsAdapter.md new file mode 100644 index 00000000000..d586d0069b1 --- /dev/null +++ b/modules/zeta_global_sspAnalyticsAdapter.md @@ -0,0 +1,24 @@ +# Zeta Global SSP Analytics Adapter + +## Overview + +Module Name: Zeta Global SSP Analytics Adapter\ +Module Type: Analytics Adapter\ +Maintainer: abermanov@zetaglobal.com + +## Description + +Analytics Adapter which sends auctionEnd and adRenderSucceeded events to Zeta Global SSP analytics endpoints + +## How to configure +``` +pbjs.enableAnalytics({ + provider: 'zeta_global_ssp', + options: { + sid: 111, + tags: { + ... + } + } +}); +``` diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js new file mode 100644 index 00000000000..1407b92c481 --- /dev/null +++ b/modules/zeta_global_sspBidAdapter.js @@ -0,0 +1,309 @@ +import {deepAccess, deepSetValue, isArray, isBoolean, isNumber, isStr, logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; + +const BIDDER_CODE = 'zeta_global_ssp'; +const ENDPOINT_URL = 'https://ssp.disqus.com/bid'; +const USER_SYNC_URL_IFRAME = 'https://ssp.disqus.com/sync?type=iframe'; +const USER_SYNC_URL_IMAGE = 'https://ssp.disqus.com/sync?type=image'; +const DEFAULT_CUR = 'USD'; +const TTL = 200; +const NET_REV = true; + +const VIDEO_REGEX = new RegExp(/VAST\s+version/); + +const DATA_TYPES = { + 'NUMBER': 'number', + 'STRING': 'string', + 'BOOLEAN': 'boolean', + 'ARRAY': 'array', + 'OBJECT': 'object' +}; +const VIDEO_CUSTOM_PARAMS = { + 'mimes': DATA_TYPES.ARRAY, + 'minduration': DATA_TYPES.NUMBER, + 'maxduration': DATA_TYPES.NUMBER, + 'startdelay': DATA_TYPES.NUMBER, + 'playbackmethod': DATA_TYPES.ARRAY, + 'api': DATA_TYPES.ARRAY, + 'protocols': DATA_TYPES.ARRAY, + 'w': DATA_TYPES.NUMBER, + 'h': DATA_TYPES.NUMBER, + 'battr': DATA_TYPES.ARRAY, + 'linearity': DATA_TYPES.NUMBER, + 'placement': DATA_TYPES.NUMBER, + 'minbitrate': DATA_TYPES.NUMBER, + 'maxbitrate': DATA_TYPES.NUMBER, + 'skip': DATA_TYPES.NUMBER +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + // check for all required bid fields + if (!(bid && + bid.bidId && + bid.params)) { + logWarn('Invalid bid request - missing required bid data'); + return false; + } + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {Bids[]} validBidRequests - an array of bidRequest objects + * @param {BidderRequest} bidderRequest - master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const secure = 1; // treat all requests as secure + const request = validBidRequests[0]; + const params = request.params; + const impData = { + id: request.bidId, + secure: secure + }; + if (request.mediaTypes) { + for (const mediaType in request.mediaTypes) { + switch (mediaType) { + case BANNER: + impData.banner = buildBanner(request); + break; + case VIDEO: + impData.video = buildVideo(request); + break; + } + } + } + if (!impData.banner && !impData.video) { + impData.banner = buildBanner(request); + } + const fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + let payload = { + id: bidderRequest.auctionId, + cur: [DEFAULT_CUR], + imp: [impData], + site: params.site ? params.site : {}, + device: {...fpd.device, ...params.device}, + user: params.user ? params.user : {}, + app: params.app ? params.app : {}, + ext: { + tags: params.tags ? params.tags : {}, + sid: params.sid ? params.sid : undefined + } + }; + const rInfo = bidderRequest.refererInfo; + payload.site.page = config.getConfig('pageUrl') || ((rInfo && rInfo.referer) ? rInfo.referer.trim() : window.location.href); + payload.site.domain = config.getConfig('publisherDomain') || getDomainFromURL(payload.site.page); + + payload.device.ua = navigator.userAgent; + payload.device.language = navigator.language; + payload.device.devicetype = isMobile() ? 1 : isConnectedTV() ? 3 : 2; + payload.site.mobile = payload.device.devicetype === 1 ? 1 : 0; + + if (params.test) { + payload.test = params.test; + } + + // Attaching GDPR Consent Params + if (bidderRequest && bidderRequest.gdprConsent) { + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest && bidderRequest.uspConsent) { + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + provideEids(request, payload); + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(payload), + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest The payload from the server's response. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + let bidResponses = []; + const response = (serverResponse || {}).body; + if (response && response.seatbid && response.seatbid[0].bid && response.seatbid[0].bid.length) { + response.seatbid.forEach(zetaSeatbid => { + zetaSeatbid.bid.forEach(zetaBid => { + let bid = { + requestId: zetaBid.impid, + cpm: zetaBid.price, + currency: response.cur, + width: zetaBid.w, + height: zetaBid.h, + ad: zetaBid.adm, + ttl: TTL, + creativeId: zetaBid.crid, + netRevenue: NET_REV, + }; + if (zetaBid.adomain && zetaBid.adomain.length) { + bid.meta = { + advertiserDomains: zetaBid.adomain + }; + } + provideMediaType(zetaBid, bid); + bidResponses.push(bid); + }) + }) + } + return bidResponses; + }, + + /** + * Register User Sync. + */ + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { + let syncurl = ''; + + // Attaching GDPR Consent Params in UserSync url + if (gdprConsent) { + syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + + // CCPA + if (uspConsent) { + syncurl += '&us_privacy=' + encodeURIComponent(uspConsent); + } + + // coppa compliance + if (config.getConfig('coppa') === true) { + syncurl += '&coppa=1'; + } + + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USER_SYNC_URL_IFRAME + syncurl + }]; + } else { + return [{ + type: 'image', + url: USER_SYNC_URL_IMAGE + syncurl + }]; + } + } +} + +function buildBanner(request) { + let sizes = request.sizes; + if (request.mediaTypes && + request.mediaTypes.banner && + request.mediaTypes.banner.sizes) { + sizes = request.mediaTypes.banner.sizes; + } + return { + w: sizes[0][0], + h: sizes[0][1] + }; +} + +function buildVideo(request) { + let video = {}; + const videoParams = deepAccess(request, 'mediaTypes.video', {}); + for (const key in VIDEO_CUSTOM_PARAMS) { + if (videoParams.hasOwnProperty(key)) { + video[key] = checkParamDataType(key, videoParams[key], VIDEO_CUSTOM_PARAMS[key]); + } + } + if (videoParams.playerSize) { + if (isArray(videoParams.playerSize[0])) { + video.w = parseInt(videoParams.playerSize[0][0], 10); + video.h = parseInt(videoParams.playerSize[0][1], 10); + } else if (isNumber(videoParams.playerSize[0])) { + video.w = parseInt(videoParams.playerSize[0], 10); + video.h = parseInt(videoParams.playerSize[1], 10); + } + } + return video; +} + +function checkParamDataType(key, value, datatype) { + let functionToExecute; + switch (datatype) { + case DATA_TYPES.BOOLEAN: + functionToExecute = isBoolean; + break; + case DATA_TYPES.NUMBER: + functionToExecute = isNumber; + break; + case DATA_TYPES.STRING: + functionToExecute = isStr; + break; + case DATA_TYPES.ARRAY: + functionToExecute = isArray; + break; + } + if (functionToExecute(value)) { + return value; + } + logWarn('Ignoring param key: ' + key + ', expects ' + datatype + ', found ' + typeof value); + return undefined; +} + +function provideEids(request, payload) { + if (Array.isArray(request.userIdAsEids) && request.userIdAsEids.length > 0) { + deepSetValue(payload, 'user.ext.eids', request.userIdAsEids); + } +} + +function getDomainFromURL(url) { + let anchor = document.createElement('a'); + anchor.href = url; + let hostname = anchor.hostname; + if (hostname.indexOf('www.') === 0) { + return hostname.substring(4); + } + return hostname; +} + +function isMobile() { + return /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent); +} + +function isConnectedTV() { + return /(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i.test(navigator.userAgent); +} + +function provideMediaType(zetaBid, bid) { + if (zetaBid.ext && zetaBid.ext.bidtype) { + if (zetaBid.ext.bidtype === VIDEO) { + bid.mediaType = VIDEO; + bid.vastXml = bid.ad; + } else { + bid.mediaType = BANNER; + } + } else { + if (VIDEO_REGEX.test(bid.ad)) { + bid.mediaType = VIDEO; + bid.vastXml = bid.ad; + } else { + bid.mediaType = BANNER; + } + } +} + +registerBidder(spec); diff --git a/nightwatch.conf.js b/nightwatch.conf.js index 072114953fe..0f34e6d0989 100644 --- a/nightwatch.conf.js +++ b/nightwatch.conf.js @@ -1,37 +1,36 @@ module.exports = (function(settings) { - var browsers = require('./browsers.json'); - delete browsers['bs_ie_9_windows_7']; - - for(var browser in browsers) { - if(browsers[browser].browser === 'iphone') continue; + var browsers = require('./browsers.json'); + delete browsers['bs_ie_9_windows_7']; - var desiredCapabilities = { - "browserName": browsers[browser].browser, - "version": browsers[browser].browser_version, - "platform": browsers[browser].os, - "os": browsers[browser].os, - "os_version": browsers[browser].os_version, - "browser": browsers[browser].browser, - "browser_version": browsers[browser].browser_version, - }; + for (var browser in browsers) { + if (browsers[browser].browser === 'iphone') continue; - settings.test_settings[browser] = { - "silent": true, - "exclude":["custom-assertions","custom-commands","common","custom-reporter"], - "screenshots" : { - "enabled" : false, - "path" : "" - }, - "javascriptEnabled": true, - "acceptSslCerts": true, - "browserstack.local": true, - "browserstack.debug": true, - "browserstack.selenium_version" : "2.53.0", - "browserstack.user": "${BROWSERSTACK_USERNAME}", - "browserstack.key": "${BROWSERSTACK_KEY}" - }; - settings.test_settings[browser]['desiredCapabilities'] = desiredCapabilities; - } - return settings; + var desiredCapabilities = { + 'browserName': browsers[browser].browser, + 'version': browsers[browser].browser_version, + 'platform': browsers[browser].os, + 'os': browsers[browser].os, + 'os_version': browsers[browser].os_version, + 'browser': browsers[browser].browser, + 'browser_version': browsers[browser].browser_version, + }; + settings.test_settings[browser] = { + 'silent': true, + 'exclude': ['custom-assertions', 'custom-commands', 'common', 'custom-reporter'], + 'screenshots': { + 'enabled': false, + 'path': '' + }, + 'javascriptEnabled': true, + 'acceptSslCerts': true, + 'browserstack.local': true, + 'browserstack.debug': true, + 'browserstack.selenium_version': '2.53.0', + 'browserstack.user': `${BROWSERSTACK_USERNAME}`, + 'browserstack.key': `${BROWSERSTACK_KEY}` + }; + settings.test_settings[browser]['desiredCapabilities'] = desiredCapabilities; + } + return settings; })(require('./nightwatch.browserstack.json')); diff --git a/package-lock.json b/package-lock.json index 3c4e59cdba7..09f7dabd16f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1918 +1,2095 @@ { "name": "prebid.js", - "version": "4.39.0-pre", - "lockfileVersion": 1, + "version": "6.13.0-pre", + "lockfileVersion": 2, "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/compat-data": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.5.tgz", - "integrity": "sha512-mPVoWNzIpYJHbWje0if7Ck36bpbtTvIxOi9+6WSK9wjGEXearAqlwBoTQvVjsAY2VIwgcs8V940geY3okzRCEw==", - "dev": true, - "requires": { - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } - }, - "@babel/core": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.5.tgz", - "integrity": "sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.5", - "@babel/helper-module-transforms": "^7.10.5", - "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.10.5", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.5", - "@babel/types": "^7.10.5", + "packages": { + "": { + "name": "prebid.js", + "version": "6.13.0-pre", + "license": "Apache-2.0", + "dependencies": { + "babel-plugin-transform-object-assign": "^6.22.0", + "core-js": "^3.13.0", + "core-js-pure": "^3.13.0", + "criteo-direct-rsa-validate": "^1.1.0", + "crypto-js": "^3.3.0", + "dlv": "1.1.3", + "dset": "2.0.1", + "express": "^4.15.4", + "fun-hooks": "^0.9.9", + "just-clone": "^1.0.2", + "live-connect-js": "2.0.0" + }, + "devDependencies": { + "@babel/core": "^7.8.4", + "@babel/eslint-parser": "^7.16.5", + "@babel/preset-env": "^7.8.4", + "@jsdevtools/coverage-istanbul-loader": "^3.0.3", + "@wdio/browserstack-service": "^6.1.4", + "@wdio/cli": "^7.5.2", + "@wdio/concise-reporter": "^7.5.2", + "@wdio/local-runner": "^7.5.2", + "@wdio/mocha-framework": "^7.5.2", + "@wdio/spec-reporter": "^7.5.2", + "@wdio/sync": "^7.5.2", + "ajv": "5.5.2", + "babel-loader": "^8.0.5", + "body-parser": "^1.19.0", + "chai": "^4.2.0", + "coveralls": "^3.1.0", + "deep-equal": "^2.0.3", + "documentation": "^13.2.5", + "es5-shim": "^4.5.14", + "eslint": "^7.27.0", + "eslint-config-standard": "^10.2.1", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prebid": "file:./plugins/eslint", + "eslint-plugin-promise": "^5.1.0", + "eslint-plugin-standard": "^3.0.1", + "execa": "^1.0.0", + "faker": "^5.5.3", + "fs.extra": "^1.3.2", + "gulp": "^4.0.0", + "gulp-clean": "^0.3.2", + "gulp-concat": "^2.6.0", + "gulp-connect": "^5.7.0", + "gulp-eslint": "^4.0.0", + "gulp-footer": "^2.0.2", + "gulp-header": "^2.0.9", + "gulp-if": "^3.0.0", + "gulp-js-escape": "^1.0.1", + "gulp-replace": "^1.0.0", + "gulp-shell": "^0.8.0", + "gulp-sourcemaps": "^3.0.0", + "gulp-terser": "^2.0.1", + "gulp-util": "^3.0.0", + "is-docker": "^2.2.1", + "istanbul": "^0.4.5", + "karma": "^6.3.2", + "karma-babel-preprocessor": "^8.0.1", + "karma-browserstack-launcher": "1.4.0", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^3.1.0", + "karma-coverage": "^2.0.1", + "karma-coverage-istanbul-reporter": "^3.0.3", + "karma-es5-shim": "^0.0.4", + "karma-firefox-launcher": "^2.1.0", + "karma-ie-launcher": "^1.0.0", + "karma-mocha": "^2.0.1", + "karma-mocha-reporter": "^2.2.5", + "karma-opera-launcher": "^1.0.0", + "karma-safari-launcher": "^1.0.0", + "karma-script-launcher": "^1.0.0", + "karma-sinon": "^1.0.5", + "karma-sourcemap-loader": "^0.3.7", + "karma-spec-reporter": "^0.0.32", + "karma-webpack": "^3.0.5", + "lodash": "^4.17.21", + "mocha": "^5.0.0", + "morgan": "^1.10.0", + "opn": "^5.4.0", + "resolve-from": "^5.0.0", + "sinon": "^4.1.3", + "through2": "^4.0.2", + "url-parse": "^1.0.5", + "webdriverio": "^7.6.1", + "webpack": "^3.0.0", + "webpack-bundle-analyzer": "^3.8.0", + "webpack-stream": "^3.2.0", + "yargs": "^1.3.1" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", + "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz", + "integrity": "sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helpers": "^7.14.6", + "@babel/parser": "^7.14.6", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", + "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", + "semver": "^6.3.0", "source-map": "^0.5.0" }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "@babel/generator": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", - "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/eslint-parser": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.16.5.tgz", + "integrity": "sha512-mUqYa46lgWqHKQ33Q6LNCGp/wPR3eqOYTUixHFsfrSQqRxH0+WOzca75iEjFr5RDGH1dDz622LaHhLOzOuQRUA==", "dev": true, - "requires": { - "@babel/types": "^7.10.5", + "dependencies": { + "eslint-scope": "^5.1.1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.11.0", + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-annotate-as-pure": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", - "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", + "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", "dev": true, - "requires": { - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", - "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.14.5.tgz", + "integrity": "sha512-YTA/Twn0vBXDVGJuAX6PwW7x5zQei1luDDo2Pl6q1qZ7hVNl0RZrhHCQG/ArGpR29Vl7ETiB8eJyrvpuRp300w==", "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.4", - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-compilation-targets": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", - "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", + "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", "dev": true, - "requires": { - "@babel/compat-data": "^7.10.4", - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" + "dependencies": { + "@babel/compat-data": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "@babel/helper-create-class-features-plugin": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", - "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz", + "integrity": "sha512-Z6gsfGofTxH/+LQXqYEK45kxmcensbzmk/oi8DmaQytlQCgqNZt9XQF8iqlI/SeXWVjaMNxvYvzaYw+kh42mDg==", "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.10.5", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4" + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", - "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", + "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-regex": "^7.10.4", - "regexpu-core": "^4.7.0" + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "regexpu-core": "^4.7.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "@babel/helper-define-map": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", - "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz", + "integrity": "sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==", "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" + "dependencies": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" } }, - "@babel/helper-explode-assignable-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz", - "integrity": "sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A==", + "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, - "requires": { - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "node_modules/@babel/helper-define-polyfill-provider/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.14.5.tgz", + "integrity": "sha512-Htb24gnGJdIGT4vnRKMdoXiOIlqOLmdiUYpAQ0mYfgVT/GDm8GOYhgi4GL+hMKrkiPRohO4ts34ELFsGAPQLDQ==", "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "node_modules/@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, - "requires": { - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "node_modules/@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "dev": true, - "requires": { - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-member-expression-to-functions": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", - "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", + "node_modules/@babel/helper-hoist-variables": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", "dev": true, - "requires": { - "@babel/types": "^7.10.5" + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-module-imports": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", - "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", + "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", "dev": true, - "requires": { - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-module-transforms": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", - "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", + "node_modules/@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", + "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", "dev": true, - "requires": { - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - }, - "@babel/helper-regex": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", - "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", "dev": true, - "requires": { - "lodash": "^4.17.19" + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-remap-async-to-generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz", - "integrity": "sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-wrap-function": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.14.5.tgz", + "integrity": "sha512-rLQKdQU+HYlxBwQIj8dk4/0ENOUEhA/Z0l4hN8BexpvmSMN9oA9EagjnhnDpNsRdWCfjwa4mn/HyBXO9yhQP6A==", "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-wrap-function": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-simple-access": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", - "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "node_modules/@babel/helper-replace-supers": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", "dev": true, - "requires": { - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-split-export-declaration": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", - "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "node_modules/@babel/helper-simple-access": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", + "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", "dev": true, - "requires": { - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", - "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.14.5.tgz", + "integrity": "sha512-dmqZB7mrb94PZSAOYtr+ZN5qt5owZIAgqtoTuqiFbHFtxgEcmQlRJVI+bO++fciBunXtB6MK7HrzrfcAzIz2NQ==", "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helpers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", - "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "dev": true, - "requires": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "engines": { + "node": ">=6.9.0" } }, - "@babel/parser": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", - "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", - "dev": true - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", - "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", + "node_modules/@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4", - "@babel/plugin-syntax-async-generators": "^7.8.0" + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-proposal-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", - "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", + "node_modules/@babel/helper-wrap-function": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.14.5.tgz", + "integrity": "sha512-YEdjTCq+LNuNS1WfxsDCNpgXkJaIyqco6DAelTUjT4f2KIWC1nBcaCaSdHTBqQVLnTBexBcVcFhLSU1KnYuePQ==", "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-function-name": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", - "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", + "node_modules/@babel/helpers": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz", + "integrity": "sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" + "dependencies": { + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-proposal-json-strings": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", - "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", + "node_modules/@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.0" + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", - "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", + "node_modules/@babel/parser": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", + "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" } }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", - "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.14.5.tgz", + "integrity": "sha512-ZoJS2XCKPBfTmL122iP6NM9dOg+d4lc9fFk3zxc8iDjvt8Pk4+TlsHSKhIPf6X+L5ORCdBzqMZDjL/WHj7WknQ==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", + "@babel/plugin-proposal-optional-chaining": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" } }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz", - "integrity": "sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA==", + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz", + "integrity": "sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-remap-async-to-generator": "^7.14.5", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", - "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz", + "integrity": "sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz", - "integrity": "sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ==", + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.14.5.tgz", + "integrity": "sha512-KBAH5ksEnYHCegqseI5N9skTdxgJdmDoAOc0uXa+4QMYKeZD0w5IARh4FMlTNtaHhbB8v+KzMdTgxMMzsIy6Yg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" } }, - "@babel/plugin-proposal-private-methods": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", - "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz", + "integrity": "sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g==", "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", - "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz", + "integrity": "sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA==", "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz", + "integrity": "sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz", + "integrity": "sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz", + "integrity": "sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.5.tgz", + "integrity": "sha512-yiclALKe0vyZRZE0pS6RXgjUOt87GWv6FYa5zqj15PvhOGFO69R5DusPlgK/1K5dVnCtegTiWu9UaBSrLLJJBg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz", + "integrity": "sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.14.7", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz", + "integrity": "sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz", + "integrity": "sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz", + "integrity": "sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-62EyfyA3WA0mZiF2e2IV9mc9Ghwxcg8YTu8BS4Wss4Y3PY725OmS9M0qLORbJwLqFtGh+jiE4wAmocK2CTUK2Q==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz", + "integrity": "sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", - "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-dynamic-import": { + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-json-strings": { + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-nullish-coalescing-operator": { + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-numeric-separator": { + "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-object-rest-spread": { + "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-optional-catch-binding": { + "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-optional-chaining": { + "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", - "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", - "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", - "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz", + "integrity": "sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==", "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", - "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", + "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-remap-async-to-generator": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-block-scoping": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz", - "integrity": "sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz", + "integrity": "sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-classes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", - "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.5.tgz", + "integrity": "sha512-LBYm4ZocNgoCqyxMLoOnwpsmQ18HWTQvql64t3GvMUzLQrNoV1BDG0lNftC8QKYERkZgCCT/7J5xWGObGAyHDw==", "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-define-map": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.5.tgz", + "integrity": "sha512-J4VxKAMykM06K/64z9rwiL6xnBHgB1+FVspqvlgCdwD1KUbQNfszeKVVOMh59w3sztHYIZDgnhOC4WbdEfHFDA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-computed-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", - "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz", + "integrity": "sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-destructuring": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", - "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", + "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", - "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz", + "integrity": "sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw==", "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", - "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz", + "integrity": "sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", - "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", + "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-for-of": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", - "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.14.5.tgz", + "integrity": "sha512-CfmqxSUZzBl0rSjpoQSFoR9UEj3HzbGuGNL21/iFTmjb5gFggJp3ph0xR1YBhexmLoKRHzgxuFvty2xdSt6gTA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", - "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz", + "integrity": "sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ==", "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", - "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", + "node_modules/@babel/plugin-transform-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz", + "integrity": "sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", - "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz", + "integrity": "sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-modules-amd": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", - "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz", + "integrity": "sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g==", "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.5", - "@babel/helper-plugin-utils": "^7.10.4", + "dependencies": { + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", - "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.5.tgz", + "integrity": "sha512-en8GfBtgnydoao2PS+87mKyw62k02k7kJ9ltbKe0fXTHrQmG6QZZflYuGI1VVG7sVpx4E1n7KBpNlPb8m78J+A==", "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", + "dependencies": { + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", - "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.14.5.tgz", + "integrity": "sha512-mNMQdvBEE5DcMQaL5LbzXFMANrQjd2W7FPzg34Y4yEz7dBgdaC+9B84dSO+/1Wba98zoDbInctCDo4JGxz1VYA==", "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.10.5", - "@babel/helper-plugin-utils": "^7.10.4", + "dependencies": { + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-modules-umd": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", - "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz", + "integrity": "sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA==", "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", - "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz", + "integrity": "sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg==", "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4" + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "@babel/plugin-transform-new-target": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", - "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz", + "integrity": "sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-object-super": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", - "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz", + "integrity": "sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-parameters": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", - "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.5.tgz", + "integrity": "sha512-Tl7LWdr6HUxTmzQtzuU14SqbgrSKmaR77M0OKyq4njZLQTPfOvzblNKyNkGwOfEFCEx7KeYHQHDI0P3F02IVkA==", "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-property-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", - "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz", + "integrity": "sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-regenerator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", - "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz", + "integrity": "sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==", "dev": true, - "requires": { + "dependencies": { "regenerator-transform": "^0.14.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-reserved-words": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", - "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz", + "integrity": "sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", - "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", + "integrity": "sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-spread": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz", - "integrity": "sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ==", + "node_modules/@babel/plugin-transform-spread": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz", + "integrity": "sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", - "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz", + "integrity": "sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-regex": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-template-literals": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", - "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz", + "integrity": "sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==", "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", - "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz", + "integrity": "sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", - "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.14.5.tgz", + "integrity": "sha512-crTo4jATEOjxj7bt9lbYXcBAM3LZaUrbP2uUdxb6WIorLmjNKSpHfIybgY4B8SRpbf8tEVIWH3Vtm7ayCrKocA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", - "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz", + "integrity": "sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==", "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/preset-env": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.4.tgz", - "integrity": "sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.10.4", - "@babel/helper-compilation-targets": "^7.10.4", - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-proposal-async-generator-functions": "^7.10.4", - "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/plugin-proposal-dynamic-import": "^7.10.4", - "@babel/plugin-proposal-json-strings": "^7.10.4", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", - "@babel/plugin-proposal-numeric-separator": "^7.10.4", - "@babel/plugin-proposal-object-rest-spread": "^7.10.4", - "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", - "@babel/plugin-proposal-optional-chaining": "^7.10.4", - "@babel/plugin-proposal-private-methods": "^7.10.4", - "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "node_modules/@babel/preset-env": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.7.tgz", + "integrity": "sha512-itOGqCKLsSUl0Y+1nSfhbuuOlTs0MJk2Iv7iSH+XT/mR8U1zRLO7NjWlYXB47yhK4J/7j+HYty/EhFZDYKa/VA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.14.7", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.14.5", + "@babel/plugin-proposal-async-generator-functions": "^7.14.7", + "@babel/plugin-proposal-class-properties": "^7.14.5", + "@babel/plugin-proposal-class-static-block": "^7.14.5", + "@babel/plugin-proposal-dynamic-import": "^7.14.5", + "@babel/plugin-proposal-export-namespace-from": "^7.14.5", + "@babel/plugin-proposal-json-strings": "^7.14.5", + "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", + "@babel/plugin-proposal-numeric-separator": "^7.14.5", + "@babel/plugin-proposal-object-rest-spread": "^7.14.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.14.5", + "@babel/plugin-proposal-optional-chaining": "^7.14.5", + "@babel/plugin-proposal-private-methods": "^7.14.5", + "@babel/plugin-proposal-private-property-in-object": "^7.14.5", + "@babel/plugin-proposal-unicode-property-regex": "^7.14.5", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.10.4", - "@babel/plugin-transform-arrow-functions": "^7.10.4", - "@babel/plugin-transform-async-to-generator": "^7.10.4", - "@babel/plugin-transform-block-scoped-functions": "^7.10.4", - "@babel/plugin-transform-block-scoping": "^7.10.4", - "@babel/plugin-transform-classes": "^7.10.4", - "@babel/plugin-transform-computed-properties": "^7.10.4", - "@babel/plugin-transform-destructuring": "^7.10.4", - "@babel/plugin-transform-dotall-regex": "^7.10.4", - "@babel/plugin-transform-duplicate-keys": "^7.10.4", - "@babel/plugin-transform-exponentiation-operator": "^7.10.4", - "@babel/plugin-transform-for-of": "^7.10.4", - "@babel/plugin-transform-function-name": "^7.10.4", - "@babel/plugin-transform-literals": "^7.10.4", - "@babel/plugin-transform-member-expression-literals": "^7.10.4", - "@babel/plugin-transform-modules-amd": "^7.10.4", - "@babel/plugin-transform-modules-commonjs": "^7.10.4", - "@babel/plugin-transform-modules-systemjs": "^7.10.4", - "@babel/plugin-transform-modules-umd": "^7.10.4", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", - "@babel/plugin-transform-new-target": "^7.10.4", - "@babel/plugin-transform-object-super": "^7.10.4", - "@babel/plugin-transform-parameters": "^7.10.4", - "@babel/plugin-transform-property-literals": "^7.10.4", - "@babel/plugin-transform-regenerator": "^7.10.4", - "@babel/plugin-transform-reserved-words": "^7.10.4", - "@babel/plugin-transform-shorthand-properties": "^7.10.4", - "@babel/plugin-transform-spread": "^7.10.4", - "@babel/plugin-transform-sticky-regex": "^7.10.4", - "@babel/plugin-transform-template-literals": "^7.10.4", - "@babel/plugin-transform-typeof-symbol": "^7.10.4", - "@babel/plugin-transform-unicode-escapes": "^7.10.4", - "@babel/plugin-transform-unicode-regex": "^7.10.4", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.10.4", - "browserslist": "^4.12.0", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.14.5", + "@babel/plugin-transform-async-to-generator": "^7.14.5", + "@babel/plugin-transform-block-scoped-functions": "^7.14.5", + "@babel/plugin-transform-block-scoping": "^7.14.5", + "@babel/plugin-transform-classes": "^7.14.5", + "@babel/plugin-transform-computed-properties": "^7.14.5", + "@babel/plugin-transform-destructuring": "^7.14.7", + "@babel/plugin-transform-dotall-regex": "^7.14.5", + "@babel/plugin-transform-duplicate-keys": "^7.14.5", + "@babel/plugin-transform-exponentiation-operator": "^7.14.5", + "@babel/plugin-transform-for-of": "^7.14.5", + "@babel/plugin-transform-function-name": "^7.14.5", + "@babel/plugin-transform-literals": "^7.14.5", + "@babel/plugin-transform-member-expression-literals": "^7.14.5", + "@babel/plugin-transform-modules-amd": "^7.14.5", + "@babel/plugin-transform-modules-commonjs": "^7.14.5", + "@babel/plugin-transform-modules-systemjs": "^7.14.5", + "@babel/plugin-transform-modules-umd": "^7.14.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.14.7", + "@babel/plugin-transform-new-target": "^7.14.5", + "@babel/plugin-transform-object-super": "^7.14.5", + "@babel/plugin-transform-parameters": "^7.14.5", + "@babel/plugin-transform-property-literals": "^7.14.5", + "@babel/plugin-transform-regenerator": "^7.14.5", + "@babel/plugin-transform-reserved-words": "^7.14.5", + "@babel/plugin-transform-shorthand-properties": "^7.14.5", + "@babel/plugin-transform-spread": "^7.14.6", + "@babel/plugin-transform-sticky-regex": "^7.14.5", + "@babel/plugin-transform-template-literals": "^7.14.5", + "@babel/plugin-transform-typeof-symbol": "^7.14.5", + "@babel/plugin-transform-unicode-escapes": "^7.14.5", + "@babel/plugin-transform-unicode-regex": "^7.14.5", + "@babel/preset-modules": "^0.1.4", + "@babel/types": "^7.14.5", + "babel-plugin-polyfill-corejs2": "^0.2.2", + "babel-plugin-polyfill-corejs3": "^0.2.2", + "babel-plugin-polyfill-regenerator": "^0.2.2", + "core-js-compat": "^3.15.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "node_modules/@babel/preset-modules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", "@babel/plugin-transform-dotall-regex": "^7.4.4", "@babel/types": "^7.4.4", "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/runtime": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz", - "integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==", + "node_modules/@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", "dev": true, - "requires": { + "dependencies": { "regenerator-runtime": "^0.13.4" }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.7.tgz", + "integrity": "sha512-Wvzcw4mBYbTagyBVZpAJWI06auSIj033T/yNE0Zn1xcup83MieCddZA7ls3kme17L4NOGBrQ09Q+nKB41RLWBA==", + "dev": true, "dependencies": { - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - } + "core-js-pure": "^3.15.0", + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "node_modules/@babel/runtime-corejs3/node_modules/regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + }, + "node_modules/@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/traverse": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", - "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", + "node_modules/@babel/traverse": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", + "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", - "@babel/parser": "^7.10.5", - "@babel/types": "^7.10.5", + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.7", + "@babel/types": "^7.14.5", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" + "globals": "^11.1.0" }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "@babel/types": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", - "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "node_modules/@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", "dev": true, - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "@gulp-sourcemaps/identity-map": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", - "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "requires": { - "acorn": "^5.0.3", - "css": "^2.2.1", - "normalize-path": "^2.1.1", - "source-map": "^0.6.0", - "through2": "^2.0.3" + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "@gulp-sourcemaps/map-sources": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", - "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", "dev": true, - "requires": { - "normalize-path": "^2.0.1", - "through2": "^2.0.3" + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "@jest/console": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", - "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "requires": { - "@jest/source-map": "^24.9.0", - "chalk": "^2.0.1", - "slash": "^2.0.0" + "engines": { + "node": ">=10" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@gulp-sourcemaps/identity-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", + "integrity": "sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==", + "dev": true, "dependencies": { - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } + "acorn": "^6.4.1", + "normalize-path": "^3.0.0", + "postcss": "^7.0.16", + "source-map": "^0.6.0", + "through2": "^3.0.1" + }, + "engines": { + "node": ">= 0.10" } }, - "@jest/core": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz", - "integrity": "sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==", + "node_modules/@gulp-sourcemaps/identity-map/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true, - "requires": { - "@jest/console": "^24.7.1", - "@jest/reporters": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "graceful-fs": "^4.1.15", - "jest-changed-files": "^24.9.0", - "jest-config": "^24.9.0", - "jest-haste-map": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.9.0", - "jest-resolve-dependencies": "^24.9.0", - "jest-runner": "^24.9.0", - "jest-runtime": "^24.9.0", - "jest-snapshot": "^24.9.0", - "jest-util": "^24.9.0", - "jest-validate": "^24.9.0", - "jest-watcher": "^24.9.0", - "micromatch": "^3.1.10", - "p-each-series": "^1.0.0", - "realpath-native": "^1.1.0", - "rimraf": "^2.5.4", - "slash": "^2.0.0", - "strip-ansi": "^5.0.0" + "bin": { + "acorn": "bin/acorn" }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "jest-message-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", - "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-regex-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", - "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" } }, - "@jest/environment": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz", - "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==", + "node_modules/@gulp-sourcemaps/identity-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "requires": { - "@jest/fake-timers": "^24.9.0", - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "jest-mock": "^24.9.0" + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } + "inherits": "^2.0.4", + "readable-stream": "2 || 3" } }, - "@jest/fake-timers": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", - "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", + "node_modules/@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", "dev": true, - "requires": { - "@jest/types": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-mock": "^24.9.0" + "dependencies": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "jest-message-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", - "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", - "dev": true - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "@jest/reporters": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz", - "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==", + "node_modules/@gulp-sourcemaps/map-sources/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, - "requires": { - "@jest/environment": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.2", - "istanbul-lib-coverage": "^2.0.2", - "istanbul-lib-instrument": "^3.0.1", - "istanbul-lib-report": "^2.0.4", - "istanbul-lib-source-maps": "^3.0.1", - "istanbul-reports": "^2.2.6", - "jest-haste-map": "^24.9.0", - "jest-resolve": "^24.9.0", - "jest-runtime": "^24.9.0", - "jest-util": "^24.9.0", - "jest-worker": "^24.6.0", - "node-notifier": "^5.4.2", - "slash": "^2.0.0", - "source-map": "^0.6.0", - "string-length": "^2.0.0" - }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", - "dev": true, - "requires": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "@jest/source-map": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", - "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", + "node_modules/@gulp-sourcemaps/map-sources/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.1.15", - "source-map": "^0.6.0" - }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "safe-buffer": "~5.1.0" } }, - "@jest/test-result": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", - "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", + "node_modules/@gulp-sourcemaps/map-sources/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, - "requires": { - "@jest/console": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/istanbul-lib-coverage": "^2.0.0" - }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "@jest/test-sequencer": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz", - "integrity": "sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "requires": { - "@jest/test-result": "^24.9.0", - "jest-haste-map": "^24.9.0", - "jest-runner": "^24.9.0", - "jest-runtime": "^24.9.0" + "engines": { + "node": ">=8" } }, - "@jest/transform": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", - "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", + "node_modules/@jest/types": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.2.tgz", + "integrity": "sha512-XpjCtJ/99HB4PmyJ2vgmN7vT+JLP7RW1FBT9RgnMFS4Dt7cvIyBee8O3/j98aUZ34ZpenPZFqmaaObWSeL65dg==", "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^24.9.0", - "babel-plugin-istanbul": "^5.1.0", - "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.15", - "jest-haste-map": "^24.9.0", - "jest-regex-util": "^24.9.0", - "jest-util": "^24.9.0", - "micromatch": "^3.1.10", - "pirates": "^4.0.1", - "realpath-native": "^1.1.0", - "slash": "^2.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "2.4.1" - }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "jest-regex-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", - "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "@jest/types": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.1.0.tgz", - "integrity": "sha512-GXigDDsp6ZlNMhXQDeuy/iYCDsRIHJabWtDzvnn36+aqFfG14JmFV0e/iXxY4SP9vbXSiPNOWdehU5MeqrYHBQ==", - "dev": true, - "requires": { "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", "chalk": "^4.0.0" }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "@jsdevtools/coverage-istanbul-loader": { + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jsdevtools/coverage-istanbul-loader": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz", "integrity": "sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==", "dev": true, - "requires": { + "dependencies": { "convert-source-map": "^1.7.0", "istanbul-lib-instrument": "^4.0.3", "loader-utils": "^2.0.0", @@ -1920,2839 +2097,2821 @@ "schema-utils": "^2.7.0" } }, - "@sindresorhus/is": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.0.0.tgz", - "integrity": "sha512-kqA5I6Yun7PBHk8WN9BBP1c7FfN2SrD05GuVSEYPqDb4nerv7HqYfgBfMIKmT/EuejURkJKLZuLyGKGs6WEG9w==", - "dev": true + "node_modules/@sindresorhus/is": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } }, - "@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", "dev": true, - "requires": { + "dependencies": { "type-detect": "4.0.8" } }, - "@sinonjs/formatio": { + "node_modules/@sinonjs/formatio": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", "dev": true, - "requires": { + "dependencies": { "samsam": "1.3.0" } }, - "@sinonjs/samsam": { + "node_modules/@sinonjs/samsam": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", "dev": true, - "requires": { + "dependencies": { "@sinonjs/commons": "^1.3.0", "array-from": "^2.1.1", "lodash": "^4.17.15" } }, - "@sinonjs/text-encoding": { + "node_modules/@sinonjs/text-encoding": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "@szmarczak/http-timer": { + "node_modules/@szmarczak/http-timer": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", "dev": true, - "requires": { + "dependencies": { "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" } }, - "@types/babel__core": { - "version": "7.1.9", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", - "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", - "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", - "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz", - "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } + "node_modules/@types/aria-query": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz", + "integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==", + "dev": true }, - "@types/cacheable-request": { + "node_modules/@types/cacheable-request": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", "dev": true, - "requires": { + "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "*", "@types/node": "*", "@types/responselike": "*" } }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "node_modules/@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==", "dev": true }, - "@types/http-cache-semantics": { + "node_modules/@types/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", + "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==", + "dev": true + }, + "node_modules/@types/easy-table": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/easy-table/-/easy-table-0.0.32.tgz", + "integrity": "sha512-zKh0f/ixYFnr3Ldf5ZJTi1ZpnRqAynTTtVyGvWDf/TT12asE8ac98t3/WGWfFdRPp/qsccxg82C/Kl3NPNhqEw==", + "dev": true + }, + "node_modules/@types/ejs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.0.6.tgz", + "integrity": "sha512-fj1hi+ZSW0xPLrJJD+YNwIh9GZbyaIepG26E/gXvp8nCa2pYokxUYO1sK9qjGxp2g8ryZYuon7wmjpwE2cyASQ==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "0.0.48", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz", + "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==", + "dev": true, + "optional": true + }, + "node_modules/@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", + "dev": true + }, + "node_modules/@types/fibers": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/fibers/-/fibers-3.1.0.tgz", + "integrity": "sha512-1o3I9xtk2PZFxwaLCC6gTaBfBZ5rvw/DSZZPK89fwuwO6LNrzSbC6rEs1xI0bQ3fCRWmO+uNJQQeD2J56oTMDg==", + "dev": true + }, + "node_modules/@types/fs-extra": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.11.tgz", + "integrity": "sha512-mZsifGG4QeQ7hlkhO56u7zt/ycBgGxSVsFI/6lGTU34VtwkiqrrSDgw0+ygs8kFGWcXnFQWMrzF2h7TtDFNixA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", "dev": true }, - "@types/istanbul-lib-coverage": { + "node_modules/@types/inquirer": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.2.tgz", + "integrity": "sha512-EkeX/hU0SWinA2c7Qu/+6+7KbepFPYJcjankUgtA/VSY6BlVHybL0Cgyey9PDbXwhNXnNGBLU3t+MORp23RgAw==", + "dev": true, + "dependencies": { + "@types/through": "*", + "rxjs": "^6.4.0" + } + }, + "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", "dev": true }, - "@types/istanbul-lib-report": { + "node_modules/@types/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", "dev": true, - "requires": { + "dependencies": { "@types/istanbul-lib-coverage": "*" } }, - "@types/istanbul-reports": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", - "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*", + "dependencies": { "@types/istanbul-lib-report": "*" } }, - "@types/json-schema": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", - "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "node_modules/@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "dev": true }, - "@types/json5": { + "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, - "@types/keyv": { + "node_modules/@types/keyv": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", "dev": true, - "requires": { + "dependencies": { "@types/node": "*" } }, - "@types/node": { - "version": "14.0.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.24.tgz", - "integrity": "sha512-btt/oNOiDWcSuI721MdL8VQGnjsKjlTMdrKyTcLCKeQp/n4AAMFJ961wMbp+09y8WuGPClDEv07RIItdXKIXAA==", + "node_modules/@types/lodash": { + "version": "4.14.170", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz", + "integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==", "dev": true }, - "@types/puppeteer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-3.0.1.tgz", - "integrity": "sha512-t03eNKCvWJXhQ8wkc5C6GYuSqMEdKLOX0GLMGtks25YZr38wKZlKTwGM/BoAPVtdysX7Bb9tdwrDS1+NrW3RRA==", + "node_modules/@types/lodash.flattendeep": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@types/lodash.flattendeep/-/lodash.flattendeep-4.4.6.tgz", + "integrity": "sha512-uLm2MaRVlqJSGsMK0RZpP5T3KqReq+9WbYDHCUhBhp98v56hMG/Yht52bsoTSui9xz2mUvQ9NfG3LrNGDL92Ng==", "dev": true, - "requires": { + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.pickby": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/@types/lodash.pickby/-/lodash.pickby-4.6.6.tgz", + "integrity": "sha512-NFa13XxlMd9eFi0UFZFWIztpMpXhozbijrx3Yb1viYZphT7jyopIFVoIRF4eYMjruWNEG1rnyrRmg/8ej9T8Iw==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.union": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/@types/lodash.union/-/lodash.union-4.6.6.tgz", + "integrity": "sha512-Wu0ZEVNcyCz8eAn6TlUbYWZoGbH9E+iOHxAZbwUoCEXdUiy6qpcz5o44mMXViM4vlPLLCPlkAubEP1gokoSZaw==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/mdast": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz", + "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/minimist": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", + "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.2.tgz", + "integrity": "sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "15.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz", + "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==", + "dev": true + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "node_modules/@types/puppeteer": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz", + "integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==", + "dev": true, + "dependencies": { "@types/node": "*" } }, - "@types/responselike": { + "node_modules/@types/recursive-readdir": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/recursive-readdir/-/recursive-readdir-2.2.0.tgz", + "integrity": "sha512-HGk753KRu2N4mWduovY4BLjYq4jTOL29gV2OfGdGxHcPSWGFkC5RRIdk+VTs5XmYd7MVAD+JwKrcb5+5Y7FOCg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", "dev": true, - "requires": { + "dependencies": { "@types/node": "*" } }, - "@types/stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "node_modules/@types/stack-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", "dev": true }, - "@types/yargs": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", - "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "node_modules/@types/stream-buffers": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.3.tgz", + "integrity": "sha512-NeFeX7YfFZDYsCfbuaOmFQ0OjSmHreKBpp7MQ4alWQBHeh2USLsj7qyMyn9t82kjqIX516CR/5SRHnARduRtbQ==", "dev": true, - "requires": { + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/through": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", + "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", + "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", + "dev": true + }, + "node_modules/@types/vinyl": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.4.tgz", + "integrity": "sha512-2o6a2ixaVI2EbwBPg1QYLGQoHK56p/8X/sGfKbFC8N6sY9lfjsMf/GprtkQkSya0D4uRiutRZ2BWj7k3JvLsAQ==", + "dev": true, + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, + "node_modules/@types/which": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", + "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", + "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", + "dev": true, + "dependencies": { "@types/yargs-parser": "*" } }, - "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", + "node_modules/@types/yargs-parser": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", + "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", "dev": true }, - "@types/yauzl": { + "node_modules/@types/yauzl": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", "dev": true, "optional": true, - "requires": { + "dependencies": { "@types/node": "*" } }, - "@wdio/browserstack-service": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-6.1.15.tgz", - "integrity": "sha512-q8qLa44wGSB3tIuZ0yquvAZqr2W7vEwupWiOd1ct0CSYgd4yX/nLd8oypqJCc8jU1ZwNAhu+V3/6hszvwx+HbA==", + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/@vue/compiler-core": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.1.2.tgz", + "integrity": "sha512-nHmq7vLjq/XM2IMbZUcKWoH5sPXa2uR/nIKZtjbK5F3TcbnYE/zKsrSUR9WZJ03unlwotNBX1OyxVt9HbWD7/Q==", "dev": true, - "requires": { - "@wdio/logger": "6.0.16", - "browserstack-local": "^1.4.5", - "got": "^11.0.2" + "optional": true, + "dependencies": { + "@babel/parser": "^7.12.0", + "@babel/types": "^7.12.0", + "@vue/shared": "3.1.2", + "estree-walker": "^2.0.1", + "source-map": "^0.6.1" } }, - "@wdio/cli": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-6.3.4.tgz", - "integrity": "sha512-eXA4rR6DwhNtXx1Hxknwgl7jGt/q4ZErCB8aOX9rowEoPOxwPQStd6yJcqI2QE8+AC1S72PKC4w+0WImL+M6Bw==", + "node_modules/@vue/compiler-core/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/utils": "6.3.0", - "async-exit-hook": "^2.0.1", - "chalk": "^4.0.0", - "chokidar": "^3.0.0", - "cli-spinners": "^2.1.0", - "ejs": "^3.0.1", - "fs-extra": "^9.0.0", - "inquirer": "^7.0.0", - "lodash.flattendeep": "^4.4.0", - "lodash.pickby": "^4.6.0", - "lodash.union": "^4.6.0", - "mkdirp": "^1.0.4", - "recursive-readdir": "^2.2.2", - "webdriverio": "6.3.4", - "yargs": "^15.0.1", - "yarn-install": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - } + "optional": true, + "engines": { + "node": ">=0.10.0" } }, - "@wdio/concise-reporter": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-6.3.0.tgz", - "integrity": "sha512-H7yILps+dKK1k4XoVE5HOVMpTHN321SFmjjMgtq1zfiC6Dph7Unl4ODmnyVLD5Kk3ycQ31PfOBr0QPyKnLUFiQ==", + "node_modules/@vue/compiler-dom": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.1.2.tgz", + "integrity": "sha512-k2+SWcWH0jL6WQAX7Or2ONqu5MbtTgTO0dJrvebQYzgqaKMXNI90RNeWeCxS4BnNFMDONpHBeFgbwbnDWIkmRg==", "dev": true, - "requires": { - "@wdio/reporter": "6.3.0", - "chalk": "^4.0.0", - "pretty-ms": "^7.0.0" + "optional": true, + "dependencies": { + "@vue/compiler-core": "3.1.2", + "@vue/shared": "3.1.2" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.2.tgz", + "integrity": "sha512-SeG/2+DvwejQ7oAiSx8BrDh5qOdqCYHGClPiTvVIHTfSIHiS2JjMbCANdDCjHkTOh/O7WZzo2JhdKm98bRBxTw==", + "dev": true, + "optional": true, + "dependencies": { + "@babel/parser": "^7.13.9", + "@babel/types": "^7.13.0", + "@types/estree": "^0.0.48", + "@vue/compiler-core": "3.1.2", + "@vue/compiler-dom": "3.1.2", + "@vue/compiler-ssr": "3.1.2", + "@vue/shared": "3.1.2", + "consolidate": "^0.16.0", + "estree-walker": "^2.0.1", + "hash-sum": "^2.0.0", + "lru-cache": "^5.1.1", + "magic-string": "^0.25.7", + "merge-source-map": "^1.1.0", + "postcss": "^8.1.10", + "postcss-modules": "^4.0.0", + "postcss-selector-parser": "^6.0.4", + "source-map": "^0.6.1" }, + "peerDependencies": { + "vue": "3.1.2" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "optional": true, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "yallist": "^3.0.2" } }, - "@wdio/config": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.1.14.tgz", - "integrity": "sha512-MXHMHwtkAblfnIxONs9aW//T9Fq5XIw3oH+tztcBRvNTTAIXmwHd+4sOjAwjpCdBSGs0C4kM/aTpGfwDZVURvQ==", + "node_modules/@vue/compiler-sfc/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "requires": { - "@wdio/logger": "6.0.16", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" + "optional": true, + "engines": { + "node": ">=0.10.0" } }, - "@wdio/local-runner": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-6.3.4.tgz", - "integrity": "sha512-rKEhFXiNH6H2G86JTgy2cgtEFoNBZ50gRy+P1LEhc7Ko/dAYqYMC+Sy8lnbsDzxz6IZVlbubgs+y7GRREayqoQ==", + "node_modules/@vue/compiler-sfc/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, - "requires": { - "@wdio/logger": "6.0.16", - "@wdio/repl": "6.3.0", - "@wdio/runner": "6.3.4", + "optional": true + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.2.tgz", + "integrity": "sha512-BwXo9LFk5OSWdMyZQ4bX1ELHX0Z/9F+ld/OaVnpUPzAZCHslBYLvyKUVDwv2C/lpLjRffpC2DOUEdl1+RP1aGg==", + "dev": true, + "optional": true, + "dependencies": { + "@vue/compiler-dom": "3.1.2", + "@vue/shared": "3.1.2" + } + }, + "node_modules/@vue/shared": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.2.tgz", + "integrity": "sha512-EmH/poaDWBPJaPILXNI/1fvUbArJQmmTyVCwvvyDYDFnkPoTclAbHRAtyIvqfez7jybTDn077HTNILpxlsoWhg==", + "dev": true, + "optional": true + }, + "node_modules/@wdio/browserstack-service": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-6.12.1.tgz", + "integrity": "sha512-B4zYlaE8q1Jxb6ctcuUPlKL3inwloETwks+cB9fFtVMDf/HH2Cau3Pi0CoIs8435EI+J4/1LxLHQV2uhzbBSlQ==", + "dev": true, + "dependencies": { + "@wdio/logger": "6.10.10", + "browserstack-local": "^1.4.5", + "got": "^11.0.2" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "@wdio/cli": "^6.0.1" + } + }, + "node_modules/@wdio/cli": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.7.4.tgz", + "integrity": "sha512-npgpaIpPoSpyef1Pv0ZKyB5FJAZnDJiVphgda0RysXd6J0S3mlwzXrcIoapl28mmeWI6NIvvv65u0sominhsyQ==", + "dev": true, + "dependencies": { + "@types/ejs": "^3.0.5", + "@types/fs-extra": "^9.0.4", + "@types/inquirer": "^7.3.1", + "@types/lodash.flattendeep": "^4.4.6", + "@types/lodash.pickby": "^4.6.6", + "@types/lodash.union": "^4.6.6", + "@types/recursive-readdir": "^2.2.0", + "@wdio/config": "7.7.3", + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", "async-exit-hook": "^2.0.1", - "stream-buffers": "^3.0.2" + "chalk": "^4.0.0", + "chokidar": "^3.0.0", + "cli-spinners": "^2.1.0", + "ejs": "^3.0.1", + "fs-extra": "^10.0.0", + "inquirer": "^8.0.0", + "lodash.flattendeep": "^4.4.0", + "lodash.pickby": "^4.6.0", + "lodash.union": "^4.6.0", + "mkdirp": "^1.0.4", + "recursive-readdir": "^2.2.2", + "webdriverio": "7.7.4", + "yargs": "^17.0.0", + "yarn-install": "^1.0.0" + }, + "bin": { + "wdio": "bin/wdio.js" + }, + "engines": { + "node": ">=12.0.0" } }, - "@wdio/logger": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.0.16.tgz", - "integrity": "sha512-VbH5UnQIG/3sSMV+Y38+rOdwyK9mVA9vuL7iOngoTafHwUjL1MObfN/Cex84L4mGxIgfxCu6GV48iUmSuQ7sqA==", + "node_modules/@wdio/cli/node_modules/@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", "dev": true, - "requires": { + "dependencies": { "chalk": "^4.0.0", "loglevel": "^1.6.0", "loglevel-plugin-prefix": "^0.8.4", "strip-ansi": "^6.0.0" }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "@wdio/mocha-framework": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-6.3.0.tgz", - "integrity": "sha512-3lLvzhDYWwOYmiJAjr2fm/nENq6g6uUOtkIeEQFp1kDyBQkDsH1PXGdFklQbRiQT8mAqOPhx1kvXrCA/XpWl7g==", + "node_modules/@wdio/cli/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, - "requires": { - "@wdio/logger": "6.0.16", - "@wdio/utils": "6.3.0", - "expect-webdriverio": "^1.1.5", - "mocha": "^8.0.1" + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "chokidar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", - "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.3.0" - } - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "mocha": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.0.1.tgz", - "integrity": "sha512-vefaXfdYI8+Yo8nPZQQi0QO2o+5q9UIMX1jZ1XMmK3+4+CQjc7+B0hPdUeglXiTlr8IHMVRo63IhO9Mzt6fxOg==", - "dev": true, - "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.3.1", - "debug": "3.2.6", - "diff": "4.0.2", - "escape-string-regexp": "1.0.5", - "find-up": "4.1.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "object.assign": "4.1.0", - "promise.allsettled": "1.0.2", - "serialize-javascript": "3.0.0", - "strip-json-comments": "3.0.1", - "supports-color": "7.1.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.0", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "readdirp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", - "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", - "dev": true, - "requires": { - "picomatch": "^2.0.7" - } - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "@wdio/protocols": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.3.0.tgz", - "integrity": "sha512-1GKzfyCTLW5WkFd3W7NLACih+zNWU7c8kFurbCQXDK1ko1obqJEs7ZjBr85q5XqMWburdks5rDjyml2iEB2LBg==", + "node_modules/@wdio/cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "@wdio/repl": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.3.0.tgz", - "integrity": "sha512-FT3flKOqNdZNG1uYl+QpOfdZIgKAWhLfoQ0s+wL0crLeDNIFvvM2qSDhRBRDYV7a0IFyBi8Z975WBn0dlH03Ig==", + "node_modules/@wdio/cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "@wdio/utils": "6.3.0" + "engines": { + "node": ">=8" } }, - "@wdio/reporter": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-6.3.0.tgz", - "integrity": "sha512-vbwjJvSKZUtsWtQMhuVqT7ZP6RIFAH4+ienjNwW30QPDi38OujZgxC2ZqRoZKsxck6cfTgkxrXfNaxHN0/LHKg==", + "node_modules/@wdio/cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "fs-extra": "^9.0.0" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "@wdio/runner": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-6.3.4.tgz", - "integrity": "sha512-+iOXfTODsSVf9LFBFKAEZqvPzfIClwFCKu7GGFZ7lrOF1svMNzT/0UY0ETsCBZe61Gr8xiI0wbCEly+0DbEh6w==", + "node_modules/@wdio/cli/node_modules/yargs": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz", + "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==", "dev": true, - "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/utils": "6.3.0", - "deepmerge": "^4.0.0", - "gaze": "^1.1.2", - "webdriver": "6.3.0", - "webdriverio": "6.3.4" + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=12" } }, - "@wdio/spec-reporter": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-6.3.0.tgz", - "integrity": "sha512-JGZAMcqiOloOw6xcIT5O8GORVaww6kslgH5kZGydVQyoNBj1ZKoLdEjqq2jklJsge1xsscdYdW9u9kMHwm25iA==", + "node_modules/@wdio/concise-reporter": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-7.7.3.tgz", + "integrity": "sha512-2Ix20n48N+lvvU4NzqMP7z+daG748RRsmDqdstCoBrJgXV6frvu38HVHV90U5uKt5Vmp6/QQl05A4OliaNoO9w==", "dev": true, - "requires": { - "@wdio/reporter": "6.3.0", + "dependencies": { + "@wdio/reporter": "7.7.3", + "@wdio/types": "7.7.3", "chalk": "^4.0.0", - "easy-table": "^1.1.1", "pretty-ms": "^7.0.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@wdio/cli": "^7.0.0" } }, - "@wdio/sync": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-6.3.3.tgz", - "integrity": "sha512-WNq+hhkgk9LluKLP2nQ/9+EH8HNQnROFFHvYuznxb1aKj/zhZvqWuQPpmMWhPMBSTpkdbdLCYerZWKcamYOcJQ==", + "node_modules/@wdio/concise-reporter/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "@types/puppeteer": "^3.0.1", - "@wdio/logger": "6.0.16", - "fibers": "^4.0.1" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "@wdio/utils": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.3.0.tgz", - "integrity": "sha512-PbeC5fpieamgSAHf7S58MAyraGU1qKxnHdfGMG+ZIWiIo73oo4j/57CcH6ZawQ3YC1wEc/5q+VXg7N5hvqhJOQ==", + "node_modules/@wdio/concise-reporter/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, - "requires": { - "@wdio/logger": "6.0.16" + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "node_modules/@wdio/concise-reporter/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "abab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", - "dev": true - }, - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "node_modules/@wdio/concise-reporter/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "node_modules/@wdio/concise-reporter/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" } }, - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "acorn-dynamic-import": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", - "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "node_modules/@wdio/concise-reporter/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "acorn": "^4.0.3" - }, "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", - "dev": true - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "node_modules/@wdio/config": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.7.3.tgz", + "integrity": "sha512-I8gkb5BjXLe6/9NK7OCA9Mc+A6xeGUqbYTRd4PNKdObE6HomKOxw4plVZCYF0DlD2FCo4OGrvYGmalojFsCMdA==", "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - } + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + }, + "engines": { + "node": ">=12.0.0" } }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "node_modules/@wdio/config/node_modules/@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", "dev": true, - "requires": { - "acorn": "^3.0.4" + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", - "dev": true + "node_modules/@wdio/config/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true + "node_modules/@wdio/config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "node_modules/@wdio/config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "node_modules/@wdio/config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - }, - "dependencies": { - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - } + "engines": { + "node": ">=8" } }, - "ajv-keywords": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.1.tgz", - "integrity": "sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA==", - "dev": true + "node_modules/@wdio/config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "node_modules/@wdio/local-runner": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.7.4.tgz", + "integrity": "sha512-ubBr9+pDZuOg6i/EJdW8E71dXrE1A63+wsZH6lpdm1fFWqfRvjl+DTCdE2rtLhr44vNSmiHxIIQnCjvZXwjiFg==", "dev": true, - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" + "dependencies": { + "@types/stream-buffers": "^3.0.3", + "@wdio/logger": "7.7.0", + "@wdio/repl": "7.7.3", + "@wdio/runner": "7.7.4", + "@wdio/types": "7.7.3", + "async-exit-hook": "^2.0.1", + "split2": "^3.2.2", + "stream-buffers": "^3.0.2" }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@wdio/cli": "^7.0.0" + } + }, + "node_modules/@wdio/local-runner/node_modules/@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" } }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true + "node_modules/@wdio/local-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "ansi-colors": { + "node_modules/@wdio/local-runner/node_modules/chalk": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, - "requires": { - "type-fest": "^0.11.0" + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "node_modules/@wdio/local-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { - "ansi-wrap": "0.1.0" + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "node_modules/@wdio/local-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@wdio/local-runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "color-convert": "^1.9.0" + "engines": { + "node": ">=8" } }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "node_modules/@wdio/local-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "node_modules/@wdio/logger": { + "version": "6.10.10", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.10.10.tgz", + "integrity": "sha512-2nh0hJz9HeZE0VIEMI+oPgjr/Q37ohrR9iqsl7f7GW5ik+PnKYCT9Eab5mR1GNMG60askwbskgGC1S9ygtvrSw==", "dev": true, - "requires": { - "buffer-equal": "^1.0.0" + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" } }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", + "node_modules/@wdio/logger/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "default-require-extensions": "^1.0.0" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "archiver": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-4.0.2.tgz", - "integrity": "sha512-B9IZjlGwaxF33UN4oPbfBkyA4V1SxNLeIhR1qY8sRXSsbdUkEHrrOvwlYFPx+8uQeCe9M+FG6KgO+imDmQ79CQ==", + "node_modules/@wdio/logger/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^3.2.0", - "buffer-crc32": "^0.2.1", - "glob": "^7.1.6", - "readable-stream": "^3.6.0", - "tar-stream": "^2.1.2", - "zip-stream": "^3.0.1" - }, "dependencies": { - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "dev": true - } + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "node_modules/@wdio/logger/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "node_modules/@wdio/logger/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@wdio/logger/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "sprintf-js": "~1.0.2" + "engines": { + "node": ">=8" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "node_modules/@wdio/logger/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "node_modules/@wdio/mocha-framework": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.7.4.tgz", + "integrity": "sha512-zLhMJBAp4HOP0qGffCNSA1UBdRystn9o5y7EEQXU6Gu+ktrSOV/RU+pvd+kqHo6RfOIcwShljZVStf3zh8cY6Q==", "dev": true, - "requires": { - "make-iterator": "^1.0.0" + "dependencies": { + "@types/mocha": "^8.0.0", + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", + "expect-webdriverio": "^3.0.0", + "mocha": "^9.0.0" + }, + "engines": { + "node": ">=12.0.0" } }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "node_modules/@wdio/mocha-framework/node_modules/@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } }, - "arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "node_modules/@wdio/mocha-framework/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "make-iterator": "^1.0.0" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "node_modules/@wdio/mocha-framework/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true + "node_modules/@wdio/mocha-framework/node_modules/chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } }, - "array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", - "dev": true + "node_modules/@wdio/mocha-framework/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "node_modules/@wdio/mocha-framework/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "dev": true + "node_modules/@wdio/mocha-framework/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "node_modules/@wdio/mocha-framework/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "node_modules/@wdio/mocha-framework/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true + "node_modules/@wdio/mocha-framework/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "array-includes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "node_modules/@wdio/mocha-framework/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "is-string": "^1.0.5" + "engines": { + "node": ">=8" } }, - "array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "node_modules/@wdio/mocha-framework/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "requires": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "node_modules/@wdio/mocha-framework/node_modules/mocha": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.1.tgz", + "integrity": "sha512-9zwsavlRO+5csZu6iRtl3GHImAbhERoDsZwdRkdJ/bE+eVplmoxNKE901ZJ9LdSchYBjSCPbjKc5XvcAri2ylw==", "dev": true, - "requires": { - "is-number": "^4.0.0" + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.7", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.23", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.4", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/@wdio/mocha-framework/node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "node_modules/@wdio/mocha-framework/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "node_modules/@wdio/mocha-framework/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "requires": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true + "node_modules/@wdio/mocha-framework/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "node_modules/@wdio/mocha-framework/node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } }, - "array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "node_modules/@wdio/mocha-framework/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "array.prototype.map": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", - "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "node_modules/@wdio/mocha-framework/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.4" + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" } }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", - "dev": true + "node_modules/@wdio/mocha-framework/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "node_modules/@wdio/protocols": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.7.4.tgz", + "integrity": "sha512-gfGPOjvqUws3/dTnrXbCYP2keYE6O5BK5qHWnOEu6c7ubE4hebxV8W5c822L7ntabc1e38+diEbM+qFuIT890Q==", "dev": true, - "requires": { - "safer-buffer": "~2.1.0" + "engines": { + "node": ">=12.0.0" } }, - "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "node_modules/@wdio/repl": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.7.3.tgz", + "integrity": "sha512-7nhvUa3Zd5Ny9topJGRZwkomlveuO3RIv+jBUHgQ2jiDIGvG9MroHxKEniIbscVSsD32XFOOZY59kSpX1b50VQ==", "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - }, "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } + "@wdio/utils": "7.7.3" + }, + "engines": { + "node": ">=12.0.0" } }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "node_modules/@wdio/reporter": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.7.3.tgz", + "integrity": "sha512-zAUGgP/FZ3XF5s4RUcDGIAeum3WzkA9ll5lymytxhh/9Jj9/5c77o498ic3RGQlB8FTz+5SVmw08r7g3uekI8g==", "dev": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" + "dependencies": { + "@types/node": "^14.14.31", + "@wdio/types": "7.7.3", + "fs-extra": "^10.0.0" }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/reporter/node_modules/@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true + }, + "node_modules/@wdio/runner": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.7.4.tgz", + "integrity": "sha512-Ahfrv3TM9y2KMjWI1xKc+tnLVO+X1/Gf5QPjprmLlRxf/rSQDfX+wMmQP/g0wsLtm4pXy0kR1K/76WWvZgzSkw==", + "dev": true, "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } + "@wdio/config": "7.7.3", + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", + "deepmerge": "^4.0.0", + "gaze": "^1.1.2", + "webdriver": "7.7.4", + "webdriverio": "7.7.4" + }, + "engines": { + "node": ">=12.0.0" } }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", - "dev": true + "node_modules/@wdio/runner/node_modules/@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } }, - "async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "node_modules/@wdio/runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true + "node_modules/@wdio/runner/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, - "async-exit-hook": { + "node_modules/@wdio/runner/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", - "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", - "dev": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { - "async-done": "^1.2.2" + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "node_modules/@wdio/runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "available-typed-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", - "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "node_modules/@wdio/runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "array-filter": "^1.0.0" + "engines": { + "node": ">=8" } }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "node_modules/@wdio/runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "node_modules/@wdio/spec-reporter": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.7.3.tgz", + "integrity": "sha512-5elsNfZd3kbBaKY5IK5ZmdZsWZNSOCqXnM2fYryAh2RBoXbcXkak4D5PbLehusZhp6CQ7UpXEKf4BDDYfd0ebw==", "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - }, "dependencies": { - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - } + "@types/easy-table": "^0.0.32", + "@wdio/reporter": "7.7.3", + "@wdio/types": "7.7.3", + "chalk": "^4.0.0", + "easy-table": "^1.1.1", + "pretty-ms": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@wdio/cli": "^7.0.0" } }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "node_modules/@wdio/spec-reporter/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - }, "dependencies": { - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - } + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "babel-helper-bindify-decorators": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", - "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", + "node_modules/@wdio/spec-reporter/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "babel-helper-builder-binary-assignment-operator-visitor": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", - "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "node_modules/@wdio/spec-reporter/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { - "babel-helper-explode-assignable-expression": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "babel-helper-builder-react-jsx": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", - "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", + "node_modules/@wdio/spec-reporter/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@wdio/spec-reporter/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "esutils": "^2.0.2" + "engines": { + "node": ">=8" } }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "node_modules/@wdio/spec-reporter/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "babel-helper-define-map": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", - "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "node_modules/@wdio/sync": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-7.7.4.tgz", + "integrity": "sha512-x0ZU78Je0yl05TfwiNtkKkJZ+90y6MndR4z5n/m6ADRzSGdFOazGJSFO0h2bN8MkPRusfqYsJwB6MKftCP0URA==", "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "dependencies": { + "@types/fibers": "^3.1.0", + "@types/puppeteer": "^5.4.0", + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "fibers": "^5.0.0", + "webdriverio": "7.7.4" + }, + "engines": { + "node": ">=12.0.0" } }, - "babel-helper-explode-assignable-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", - "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "node_modules/@wdio/sync/node_modules/@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" } }, - "babel-helper-explode-class": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", - "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", + "node_modules/@wdio/sync/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "babel-helper-bindify-decorators": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "babel-helper-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "node_modules/@wdio/sync/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, - "requires": { - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "node_modules/@wdio/sync/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "node_modules/@wdio/sync/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@wdio/sync/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "engines": { + "node": ">=8" } }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "node_modules/@wdio/sync/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "babel-helper-regex": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", - "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "node_modules/@wdio/types": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.7.3.tgz", + "integrity": "sha512-ZZBQHCXKjZSQj9pf4df/QhfgQQj0vzm9hkK7YyNM+S+qnW0LExL8qQKLxTlGHDaYxk/+Jrd9pcZrJXRCoSnUaA==", "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "dependencies": { + "@types/node": "^14.14.31", + "got": "^11.8.1" + }, + "engines": { + "node": ">=12.0.0" } }, - "babel-helper-remap-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", - "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "node_modules/@wdio/types/node_modules/@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true + }, + "node_modules/@wdio/utils": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.7.3.tgz", + "integrity": "sha512-bvOoE2gve8Z8HFguVw0RMp5BbSmJR4zSr8DwbwnA8RSL3NshKlRk33HWYLmKsxjkH+ZWI2ihFbpvLD4W4imXag==", "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "dependencies": { + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3" + }, + "engines": { + "node": ">=12.0.0" } }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "node_modules/@wdio/utils/node_modules/@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", "dev": true, - "requires": { - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" } }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "node_modules/@wdio/utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "babel-jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", - "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", + "node_modules/@wdio/utils/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, - "requires": { - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/babel__core": "^7.1.0", - "babel-plugin-istanbul": "^5.1.0", - "babel-preset-jest": "^24.9.0", - "chalk": "^2.4.2", - "slash": "^2.0.0" - }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "babel-loader": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", - "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", + "node_modules/@wdio/utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { - "find-cache-dir": "^2.1.0", - "loader-utils": "^1.4.0", - "mkdirp": "^0.5.3", - "pify": "^4.0.1", - "schema-utils": "^2.6.5" - }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "node_modules/@wdio/utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@wdio/utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=8" } }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "node_modules/@wdio/utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" + "node_modules/abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" } }, - "babel-plugin-istanbul": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", - "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "find-up": "^3.0.0", - "istanbul-lib-instrument": "^3.3.0", - "test-exclude": "^5.2.3" + "bin": { + "acorn": "bin/acorn" }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", - "dev": true, - "requires": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "engines": { + "node": ">=0.4.0" } }, - "babel-plugin-jest-hoist": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz", - "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==", + "node_modules/acorn-dynamic-import": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", "dev": true, - "requires": { - "@types/babel__traverse": "^7.0.6" + "dependencies": { + "acorn": "^4.0.3" } }, - "babel-plugin-syntax-async-functions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", - "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", - "dev": true - }, - "babel-plugin-syntax-async-generators": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", - "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", - "dev": true - }, - "babel-plugin-syntax-class-constructor-call": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz", - "integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=", - "dev": true - }, - "babel-plugin-syntax-class-properties": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", - "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", - "dev": true + "node_modules/acorn-dynamic-import/node_modules/acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } }, - "babel-plugin-syntax-decorators": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", - "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", - "dev": true + "node_modules/acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } }, - "babel-plugin-syntax-do-expressions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz", - "integrity": "sha1-V0d1YTmqJtOQ0JQQsDdEugfkeW0=", - "dev": true + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } }, - "babel-plugin-syntax-dynamic-import": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", - "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", - "dev": true + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } }, - "babel-plugin-syntax-exponentiation-operator": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", - "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "node_modules/add-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", + "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=", "dev": true }, - "babel-plugin-syntax-export-extensions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz", - "integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=", - "dev": true + "node_modules/adm-zip": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.2.1.tgz", + "integrity": "sha1-6AHO3rW9mk6Y1pnFwPQjnicx3L8=", + "dev": true, + "engines": { + "node": ">=0.3.0" + } }, - "babel-plugin-syntax-flow": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", - "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", - "dev": true + "node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } }, - "babel-plugin-syntax-function-bind": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz", - "integrity": "sha1-SMSV8Xe98xqYHnMvVa3AvdJgH0Y=", - "dev": true + "node_modules/ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "dependencies": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", - "dev": true + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "node_modules/ajv/node_modules/fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", "dev": true }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "node_modules/ajv/node_modules/json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", "dev": true }, - "babel-plugin-system-import-transformer": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-system-import-transformer/-/babel-plugin-system-import-transformer-3.1.0.tgz", - "integrity": "sha1-038Mro5h7zkGAggzHZMbXmMNfF8=", + "node_modules/align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, - "requires": { - "babel-plugin-syntax-dynamic-import": "^6.18.0" + "dependencies": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-async-generator-functions": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", - "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", + "node_modules/align-text/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-generators": "^6.5.0", - "babel-runtime": "^6.22.0" + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", - "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-functions": "^6.8.0", - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=0.4.2" } }, - "babel-plugin-transform-class-constructor-call": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz", - "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true, - "requires": { - "babel-plugin-syntax-class-constructor-call": "^6.18.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "engines": { + "node": ">=6" } }, - "babel-plugin-transform-class-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", - "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-plugin-syntax-class-properties": "^6.8.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "babel-plugin-transform-decorators": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", - "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", + "node_modules/ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", "dev": true, - "requires": { - "babel-helper-explode-class": "^6.24.1", - "babel-plugin-syntax-decorators": "^6.13.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-types": "^6.24.1" + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-decorators-legacy": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.5.tgz", - "integrity": "sha512-jYHwjzRXRelYQ1uGm353zNzf3QmtdCfvJbuYTZ4gKveK7M9H1fs3a5AKdY1JUDl0z97E30ukORW1dzhWvsabtA==", + "node_modules/ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", "dev": true, - "requires": { - "babel-plugin-syntax-decorators": "^6.1.18", - "babel-runtime": "^6.2.0", - "babel-template": "^6.3.0" + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" } }, - "babel-plugin-transform-do-expressions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz", - "integrity": "sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs=", + "node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true, - "requires": { - "babel-plugin-syntax-do-expressions": "^6.8.0", - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=8" } }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", - "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "node_modules/append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", "dev": true, - "requires": { - "babel-helper-define-map": "^6.24.1", - "babel-helper-function-name": "^6.24.1", - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-helper-replace-supers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "dependencies": { + "buffer-equal": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "node_modules/archiver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" } }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" } }, - "babel-plugin-transform-es2015-duplicate-keys": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", - "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "dependencies": { + "safe-buffer": "~5.1.0" } }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } + "node_modules/archiver/node_modules/async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "dependencies": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + }, + "engines": { + "node": ">=6.0" } }, - "babel-plugin-transform-es2015-modules-amd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", - "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", - "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "node_modules/arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", "dev": true, - "requires": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-types": "^6.26.0" + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-modules-systemjs": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", - "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true, - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-modules-umd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", - "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "node_modules/arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true, - "requires": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", "dev": true, - "requires": { - "babel-helper-call-delegate": "^6.24.1", - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", - "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", + "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", "dev": true, - "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "node_modules/array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "dependencies": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", - "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "node_modules/array-initial/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", - "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "node_modules/array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", "dev": true, - "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "regexpu-core": "^2.0.0" - }, "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "regexpu-core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", - "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", - "dev": true, - "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - } - } + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-exponentiation-operator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", - "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "node_modules/array-last/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", "dev": true, - "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", - "babel-plugin-syntax-exponentiation-operator": "^6.8.0", - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-export-extensions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz", - "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", "dev": true, - "requires": { - "babel-plugin-syntax-export-extensions": "^6.8.0", - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-flow-strip-types": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", - "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", + "node_modules/array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", "dev": true, - "requires": { - "babel-plugin-syntax-flow": "^6.18.0", - "babel-runtime": "^6.22.0" + "dependencies": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-function-bind": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz", - "integrity": "sha1-xvuOlqwpajELjPjqQBRiQH3fapc=", + "node_modules/array-sort/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true, - "requires": { - "babel-plugin-syntax-function-bind": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-object-assign": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.22.0.tgz", - "integrity": "sha1-+Z0vZvGgsNSY40bFNZaEdAyqILo=", - "requires": { - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-object-rest-spread": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", - "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.8.0", - "babel-runtime": "^6.26.0" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-react-display-name": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", - "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-react-jsx": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", - "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", + "node_modules/array.prototype.flat": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", + "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", "dev": true, - "requires": { - "babel-helper-builder-react-jsx": "^6.24.1", - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "babel-plugin-transform-react-jsx-self": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", - "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true, - "requires": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-transform-react-jsx-source": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", - "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "dev": true, - "requires": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "dependencies": { + "safer-buffer": "~2.1.0" } }, - "babel-plugin-transform-regenerator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", - "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "dev": true, - "requires": { - "regenerator-transform": "^0.10.0" - }, "dependencies": { - "regenerator-transform": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", - "dev": true, - "requires": { - "babel-runtime": "^6.18.0", - "babel-types": "^6.19.0", - "private": "^0.1.6" - } - } + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" } }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "dependencies": { + "object-assign": "^4.1.1", + "util": "0.10.3" } }, - "babel-preset-env": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", - "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assert/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "node_modules/assert/node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "^6.22.0", - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "babel-plugin-transform-async-to-generator": "^6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoping": "^6.23.0", - "babel-plugin-transform-es2015-classes": "^6.23.0", - "babel-plugin-transform-es2015-computed-properties": "^6.22.0", - "babel-plugin-transform-es2015-destructuring": "^6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", - "babel-plugin-transform-es2015-for-of": "^6.23.0", - "babel-plugin-transform-es2015-function-name": "^6.22.0", - "babel-plugin-transform-es2015-literals": "^6.22.0", - "babel-plugin-transform-es2015-modules-amd": "^6.22.0", - "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", - "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", - "babel-plugin-transform-es2015-modules-umd": "^6.23.0", - "babel-plugin-transform-es2015-object-super": "^6.22.0", - "babel-plugin-transform-es2015-parameters": "^6.23.0", - "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", - "babel-plugin-transform-es2015-spread": "^6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", - "babel-plugin-transform-es2015-template-literals": "^6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", - "babel-plugin-transform-exponentiation-operator": "^6.22.0", - "babel-plugin-transform-regenerator": "^6.22.0", - "browserslist": "^3.2.6", - "invariant": "^2.2.2", - "semver": "^5.3.0" - }, "dependencies": { - "browserslist": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", - "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000844", - "electron-to-chromium": "^1.3.47" - } - } + "inherits": "2.0.1" } }, - "babel-preset-flow": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", - "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, - "requires": { - "babel-plugin-transform-flow-strip-types": "^6.22.0" + "engines": { + "node": "*" } }, - "babel-preset-jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", - "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==", + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true, - "requires": { - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "babel-plugin-jest-hoist": "^24.9.0" + "engines": { + "node": ">=0.10.0" } }, - "babel-preset-react": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", - "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, - "requires": { - "babel-plugin-syntax-jsx": "^6.3.13", - "babel-plugin-transform-react-display-name": "^6.23.0", - "babel-plugin-transform-react-jsx": "^6.24.1", - "babel-plugin-transform-react-jsx-self": "^6.22.0", - "babel-plugin-transform-react-jsx-source": "^6.22.0", - "babel-preset-flow": "^6.23.0" + "engines": { + "node": ">=8" } }, - "babel-preset-stage-0": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz", - "integrity": "sha1-VkLRUEL5E4TX5a+LyIsduVsDnmo=", + "node_modules/astw": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", + "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=", "dev": true, - "requires": { - "babel-plugin-transform-do-expressions": "^6.22.0", - "babel-plugin-transform-function-bind": "^6.22.0", - "babel-preset-stage-1": "^6.24.1" + "dependencies": { + "acorn": "^4.0.3" } }, - "babel-preset-stage-1": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz", - "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", + "node_modules/astw/node_modules/acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", "dev": true, - "requires": { - "babel-plugin-transform-class-constructor-call": "^6.24.1", - "babel-plugin-transform-export-extensions": "^6.22.0", - "babel-preset-stage-2": "^6.24.1" + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, - "babel-preset-stage-2": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", - "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", + "node_modules/async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "node_modules/async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", "dev": true, - "requires": { - "babel-plugin-syntax-dynamic-import": "^6.18.0", - "babel-plugin-transform-class-properties": "^6.24.1", - "babel-plugin-transform-decorators": "^6.24.1", - "babel-preset-stage-3": "^6.24.1" + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" } }, - "babel-preset-stage-3": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", - "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", "dev": true, - "requires": { - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "babel-plugin-transform-async-generator-functions": "^6.24.1", - "babel-plugin-transform-async-to-generator": "^6.24.1", - "babel-plugin-transform-exponentiation-operator": "^6.24.1", - "babel-plugin-transform-object-rest-spread": "^6.22.0" + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "node_modules/async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "dependencies": { + "async-done": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz", + "integrity": "sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "engines": { + "node": "*" } }, - "babel-register": { + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "node_modules/babel-code-frame": { "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" + "dependencies": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "node_modules/babel-code-frame/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "node_modules/babel-code-frame/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "node_modules/babel-code-frame/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/babel-loader": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", + "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^1.4.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" - } + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" } }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "node_modules/babel-loader/node_modules/loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" } }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", + "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.2", + "semver": "^6.1.1" }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.3.tgz", + "integrity": "sha512-rCOFzEIJpJEAU14XCcV/erIf/wZQMmMT5l5vXOpL5uoznyOGfDIjPj6FVytMvtzaKSTSVKouOCTPJ5OMUZH30g==", + "dev": true, "dependencies": { - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - } + "@babel/helper-define-polyfill-provider": "^0.2.2", + "core-js-compat": "^3.14.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", + "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.2.2" }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-transform-object-assign": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.22.0.tgz", + "integrity": "sha1-+Z0vZvGgsNSY40bFNZaEdAyqILo=", "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - } + "babel-runtime": "^6.22.0" } }, - "babelify": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/babelify/-/babelify-8.0.0.tgz", - "integrity": "sha512-xVr63fKEvMWUrrIbqlHYsMcc5Zdw4FSVesAHgkgajyCE1W8gbm9rbMakqavhxKvikGYMhEcqxTwB/gQmQ6lBtw==", - "dev": true + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true + "node_modules/babel-runtime/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true }, - "bach": { + "node_modules/babelify": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz", + "integrity": "sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", "dev": true, - "requires": { + "dependencies": { "arr-filter": "^1.1.1", "arr-flatten": "^1.0.1", "arr-map": "^2.0.0", @@ -4762,32 +4921,33 @@ "async-done": "^1.2.2", "async-settle": "^1.0.0", "now-and-later": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" } }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, - "bail": { + "node_modules/bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", - "dev": true + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "base": { + "node_modules/base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, - "requires": { + "dependencies": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", "component-emitter": "^1.2.1", @@ -4796,238 +4956,326 @@ "mixin-deep": "^1.2.0", "pascalcase": "^0.1.1" }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "engines": { + "node": ">=0.10.0" } }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "dev": true + "node_modules/base/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } }, - "base64id": { + "node_modules/base/node_modules/is-data-descriptor": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/Base64": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", + "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg=", "dev": true }, - "basic-auth": { + "node_modules/base64-arraybuffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "dev": true, - "requires": { + "dependencies": { "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" } }, - "batch": { + "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, - "bcrypt-pbkdf": { + "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, - "requires": { + "dependencies": { "tweetnacl": "^0.14.3" } }, - "beeper": { + "node_modules/beefy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/beefy/-/beefy-1.1.0.tgz", + "integrity": "sha1-SOAy/G5nJpo8jUILABdEc7jbnyA=", + "dev": true, + "dependencies": { + "chokidar": "0.8.1", + "colors": "~0.6.0-1", + "ignorepatterns": "1.0.1", + "mime": "~1.2.9", + "nopt": "~2.1.1", + "open": "0.0.3", + "portfinder": "~0.2.1", + "response-stream": "0.0.0", + "script-injector": "~0.1.0", + "sse-stream": "0.0.4", + "through": "~2.2.0" + }, + "bin": { + "beefy": "bin/beefy" + } + }, + "node_modules/beefy/node_modules/chokidar": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-0.8.1.tgz", + "integrity": "sha1-juDJnvSEIJAv3tc7hi6sLdddpKY=", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dev": true, + "hasInstallScript": true + }, + "node_modules/beefy/node_modules/colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/beefy/node_modules/mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true + }, + "node_modules/beefy/node_modules/nopt": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", + "integrity": "sha1-bMzZd7gBMqB3MdbozljCyDA8+a8=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/beefy/node_modules/through": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/through/-/through-2.2.7.tgz", + "integrity": "sha1-bo4hIAGR1OtqmfbwEN9Gqhxusr0=", + "dev": true + }, + "node_modules/beeper": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true - }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", "dev": true, - "requires": { - "callsite": "1.0.0" + "engines": { + "node": ">=0.10.0" } }, - "bfj": { + "node_modules/bfj": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", "dev": true, - "requires": { + "dependencies": { "bluebird": "^3.5.5", "check-types": "^8.0.3", "hoopy": "^0.1.4", "tryer": "^1.0.1" + }, + "engines": { + "node": ">= 6.0.0" } }, - "big-integer": { + "node_modules/big-integer": { "version": "1.6.48", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.6" + } }, - "big.js": { + "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true + "dev": true, + "engines": { + "node": "*" + } }, - "binary": { + "node_modules/binary": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", "dev": true, - "requires": { + "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" } }, - "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "dev": true + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "binaryextensions": { + "node_modules/binaryextensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.8" + }, + "funding": { + "url": "https://bevry.me/fund" + } }, - "bindings": { + "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "dev": true, "optional": true, - "requires": { + "dependencies": { "file-uri-to-path": "1.0.0" } }, - "bl": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", - "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, - "requires": { + "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - } } }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "bluebird": { + "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, - "bn.js": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", - "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==", + "node_modules/bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", "dev": true }, - "body": { + "node_modules/body": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", "dev": true, - "requires": { + "dependencies": { "continuable-cache": "^0.3.1", "error": "^7.0.0", "raw-body": "~1.1.0", "safe-json-parse": "~1.0.1" - }, - "dependencies": { - "bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", - "dev": true - }, - "raw-body": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", - "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", - "dev": true, - "requires": { - "bytes": "1", - "string_decoder": "0.10" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } } }, - "body-parser": { + "node_modules/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { + "dependencies": { "bytes": "3.1.0", "content-type": "~1.0.4", "debug": "2.6.9", @@ -5038,68 +5286,344 @@ "qs": "6.7.0", "raw-body": "2.4.0", "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" } }, - "brace-expansion": { + "node_modules/body/node_modules/bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", + "dev": true + }, + "node_modules/body/node_modules/raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", + "dev": true, + "dependencies": { + "bytes": "1", + "string_decoder": "0.10" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/body/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", + "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dev": true, + "optional": true, + "dependencies": { + "hoek": "0.9.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "braces": { + "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, - "requires": { + "dependencies": { "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" } }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true + "node_modules/brfs": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/brfs/-/brfs-0.0.9.tgz", + "integrity": "sha1-kkVbw2+74SZ6N5Z736lXEIgZaro=", + "dev": true, + "dependencies": { + "escodegen": "0.0.17", + "falafel": "~0.1.6", + "through": "~2.2.0" + }, + "bin": { + "brfs": "bin/cmd.js" + } }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true + "node_modules/brfs/node_modules/escodegen": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-0.0.17.tgz", + "integrity": "sha1-HnjRffEAT9eojy/tO4uFkvMhf5w=", + "dev": true, + "dependencies": { + "esprima": "~1.0.2", + "estraverse": "~0.0.4" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=0.4.0" + }, + "optionalDependencies": { + "source-map": ">= 0.1.2" + } }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "node_modules/brfs/node_modules/esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", "dev": true, - "requires": { - "resolve": "1.1.7" + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } + "engines": { + "node": ">=0.4.0" } }, - "browser-stdout": { + "node_modules/brfs/node_modules/estraverse": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-0.0.4.tgz", + "integrity": "sha1-AaCTLf7ldGhKWYr1pnw7+bZCjbI=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/brfs/node_modules/through": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/through/-/through-2.2.7.tgz", + "integrity": "sha1-bo4hIAGR1OtqmfbwEN9Gqhxusr0=", + "dev": true + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "node_modules/browser-pack": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-3.2.0.tgz", + "integrity": "sha1-+qHLxBSHsazEdH43PhFIrf/Q4tk=", + "dev": true, + "dependencies": { + "combine-source-map": "~0.3.0", + "concat-stream": "~1.4.1", + "defined": "~0.0.0", + "JSONStream": "~0.8.4", + "through2": "~0.5.1", + "umd": "^2.1.0" + }, + "bin": { + "browser-pack": "bin/cmd.js" + } + }, + "node_modules/browser-pack/node_modules/concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "node_modules/browser-pack/node_modules/defined": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", + "dev": true + }, + "node_modules/browser-pack/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/browser-pack/node_modules/jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/browser-pack/node_modules/JSONStream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", + "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=", + "dev": true, + "dependencies": { + "jsonparse": "0.0.5", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "index.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/browser-pack/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/browser-pack/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/browser-pack/node_modules/through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "dependencies": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + }, + "node_modules/browser-pack/node_modules/through2/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/browser-pack/node_modules/xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "dependencies": { + "resolve": "1.1.7" + } + }, + "node_modules/browser-resolve/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "browserify-aes": { + "node_modules/browserify": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-8.1.3.tgz", + "integrity": "sha1-8zpUmjpsNoIZsHX9z+bfGV4Empo=", + "dev": true, + "dependencies": { + "assert": "~1.3.0", + "browser-pack": "^3.2.0", + "browser-resolve": "^1.3.0", + "browserify-zlib": "~0.1.2", + "buffer": "^3.0.0", + "builtins": "~0.0.3", + "commondir": "0.0.1", + "concat-stream": "~1.4.1", + "console-browserify": "^1.1.0", + "constants-browserify": "~0.0.1", + "crypto-browserify": "^3.0.0", + "deep-equal": "~0.2.1", + "defined": "~0.0.0", + "deps-sort": "^1.3.5", + "domain-browser": "~1.1.0", + "duplexer2": "~0.0.2", + "events": "~1.0.0", + "glob": "^4.0.5", + "http-browserify": "^1.4.0", + "https-browserify": "~0.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^6.2.0", + "isarray": "0.0.1", + "JSONStream": "~0.8.3", + "labeled-stream-splicer": "^1.0.0", + "module-deps": "^3.6.3", + "os-browserify": "~0.1.1", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "^0.10.0", + "punycode": "~1.2.3", + "querystring-es3": "~0.2.0", + "readable-stream": "^1.0.33-1", + "resolve": "~0.7.1", + "shallow-copy": "0.0.1", + "shasum": "^1.0.0", + "shell-quote": "~0.0.1", + "stream-browserify": "^1.0.0", + "string_decoder": "~0.10.0", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^1.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "~0.0.0", + "umd": "~2.1.0", + "url": "~0.10.1", + "util": "~0.10.1", + "vm-browserify": "~0.0.1", + "xtend": "^3.0.0" + }, + "bin": { + "browserify": "bin/cmd.js" + } + }, + "node_modules/browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, - "requires": { + "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", "create-hash": "^1.1.0", @@ -5108,1979 +5632,32701 @@ "safe-buffer": "^5.0.1" } }, - "browserify-cipher": { + "node_modules/browserify-cipher": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "dev": true, - "requires": { + "dependencies": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", "evp_bytestokey": "^1.0.0" } }, - "browserify-des": { + "node_modules/browserify-des": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "dev": true, - "requires": { + "dependencies": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" } }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", "dev": true, - "requires": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" - }, "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" } }, - "browserify-sign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", - "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", + "node_modules/browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", "dev": true, - "requires": { + "dependencies": { "bn.js": "^5.1.1", "browserify-rsa": "^4.0.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.2", + "elliptic": "^6.5.3", "inherits": "^2.0.4", "parse-asn1": "^5.1.5", "readable-stream": "^3.6.0", "safe-buffer": "^5.2.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + } + }, + "node_modules/browserify-sign/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/browserify-sign/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } - } + ] }, - "browserify-zlib": { + "node_modules/browserify-zlib": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, - "requires": { + "dependencies": { "pako": "~1.0.5" } }, - "browserslist": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", - "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", + "node_modules/browserify/node_modules/assert": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.3.0.tgz", + "integrity": "sha1-A5OaYiWCqBLMICMgoLmlbJuBWEk=", "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001093", - "electron-to-chromium": "^1.3.488", - "escalade": "^3.0.1", - "node-releases": "^1.1.58" + "dependencies": { + "util": "0.10.3" } }, - "browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", + "node_modules/browserify/node_modules/assert/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "node_modules/browserify/node_modules/assert/node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - }, "dependencies": { - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "inherits": "2.0.1" } }, - "browserstack-local": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.4.5.tgz", - "integrity": "sha512-0/VdSv2YVXmcnwBb64XThMvjM1HnZJnPdv7CUgQbC5y/N9Wsr0Fu+j1oknE9fC/VPx9CpoSC6CJ0kza42skMSA==", + "node_modules/browserify/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=", "dev": true, - "requires": { - "https-proxy-agent": "^4.0.0", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" + "engines": { + "node": ">= 0.4" } }, - "browserstacktunnel-wrapper": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.4.tgz", - "integrity": "sha512-GCV599FUUxNOCFl3WgPnfc5dcqq9XTmMXoxWpqkvmk0R9TOIoqmjENNU6LY6DtgIL6WfBVbg/jmWtnM5K6UYSg==", + "node_modules/browserify/node_modules/browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1", - "unzipper": "^0.9.3" - }, "dependencies": { - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "pako": "~0.2.0" } }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "node_modules/browserify/node_modules/buffer": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.2.tgz", + "integrity": "sha512-c3M77NkHJxS0zx/ErxXhDLr1v3y2MDXPeTJPvLNOaIYJ4ymHBUFQ9EXzt9HYuqAJllMoNb/EZ8hIiulnQFAUuQ==", "dev": true, - "requires": { - "node-int64": "^0.4.0" + "dependencies": { + "base64-js": "0.0.8", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, - "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "node_modules/browserify/node_modules/buffer/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/browserify/node_modules/commondir": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-0.0.1.tgz", + "integrity": "sha1-ifAP3NUbUZxXhzP+xWPmptp/W+I=", "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "engines": { + "node": "*" } }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "node_modules/browserify/node_modules/concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", "dev": true, - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" } }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "node_modules/browserify/node_modules/constants-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-0.0.1.tgz", + "integrity": "sha1-kld9tSe6bEzwpFaNhLwDH0QeIfI=", "dev": true }, - "buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "node_modules/browserify/node_modules/deep-equal": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", + "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=", "dev": true }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "node_modules/browserify/node_modules/defined": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", "dev": true }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "node_modules/browserify/node_modules/domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true, + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } }, - "buffer-indexof-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz", - "integrity": "sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8=", - "dev": true + "node_modules/browserify/node_modules/duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "dependencies": { + "readable-stream": "~1.1.9" + } }, - "buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", - "dev": true + "node_modules/browserify/node_modules/events": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/events/-/events-1.0.2.tgz", + "integrity": "sha1-dYSdz+k9EPsFfDAFWv29UdBqjiQ=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true + "node_modules/browserify/node_modules/glob": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^2.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": "*" + } }, - "buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", + "node_modules/browserify/node_modules/https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", "dev": true }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "node_modules/browserify/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "node_modules/browserify/node_modules/jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] }, - "cac": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/cac/-/cac-3.0.4.tgz", - "integrity": "sha1-bSTO7Dcu/lybeYgIvH9JtHJCpO8=", + "node_modules/browserify/node_modules/JSONStream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", + "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=", "dev": true, - "requires": { - "camelcase-keys": "^3.0.0", - "chalk": "^1.1.3", - "indent-string": "^3.0.0", - "minimist": "^1.2.0", - "read-pkg-up": "^1.0.1", - "suffix": "^0.1.0", - "text-table": "^0.2.0" - }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "jsonparse": "0.0.5", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "index.js" + }, + "engines": { + "node": "*" } }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "node_modules/browserify/node_modules/minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "dependencies": { + "brace-expansion": "^1.0.0" + }, + "engines": { + "node": "*" } }, - "cacheable-lookup": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz", - "integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w==", + "node_modules/browserify/node_modules/os-browserify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", + "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=", "dev": true }, - "cacheable-request": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", - "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "node_modules/browserify/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "node_modules/browserify/node_modules/process": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/process/-/process-0.10.1.tgz", + "integrity": "sha1-hCRXzFHP7XLcd1r+6vuMYDQ3JyU=", "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^2.0.0" + "engines": { + "node": ">= 0.6.0" } }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "node_modules/browserify/node_modules/punycode": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.2.4.tgz", + "integrity": "sha1-VACKyXKux0F13vnLpt9/qdORh0A=", + "dev": true, + "engines": [ + "node", + "rhino" + ] + }, + "node_modules/browserify/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, - "requires": { - "callsites": "^0.2.0" - }, "dependencies": { - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - } + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "node_modules/browserify/node_modules/resolve": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.7.4.tgz", + "integrity": "sha1-OVqe+ehz+/4SvRRAi9kbuTYAPWk=", "dev": true }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "node_modules/browserify/node_modules/stream-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-1.0.0.tgz", + "integrity": "sha1-v5tKv7QrJ011FHnkTg/yZWtvEZM=", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^1.0.27-1" + } }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/browserify/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, - "camelcase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-3.0.0.tgz", - "integrity": "sha1-/AxsNgNj9zd+N5O5oWvM8QcMHKQ=", + "node_modules/browserify/node_modules/through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", "dev": true, - "requires": { - "camelcase": "^3.0.0", - "map-obj": "^1.0.0" - }, "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - } + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" } }, - "caniuse-lite": { - "version": "1.0.30001105", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001105.tgz", - "integrity": "sha512-JupOe6+dGMr7E20siZHIZQwYqrllxotAhiaej96y6x00b/48rPt42o+SzOSCPbrpsDWvRja40Hwrj0g0q6LZJg==", - "dev": true + "node_modules/browserify/node_modules/through2/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } }, - "capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "node_modules/browserify/node_modules/timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", "dev": true, - "requires": { - "rsvp": "^4.8.4" + "dependencies": { + "process": "~0.11.0" + }, + "engines": { + "node": ">=0.6.0" } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "node_modules/browserify/node_modules/timers-browserify/node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } }, - "ccount": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz", - "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==", + "node_modules/browserify/node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/browserify/node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "dev": true }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "node_modules/browserify/node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", "dev": true, - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" + "dependencies": { + "inherits": "2.0.3" } }, - "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "node_modules/browserify/node_modules/vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" + "dependencies": { + "indexof": "0.0.1" } }, - "chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "node_modules/browserify/node_modules/xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", "dev": true, - "requires": { - "traverse": ">=0.3.0 <0.4" + "engines": { + "node": ">=0.4" } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/browserslist": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "dependencies": { + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" } }, - "character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", - "dev": true - }, - "character-entities-html4": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", - "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", - "dev": true - }, - "character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "dev": true - }, - "character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", - "dev": true - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "check-types": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", - "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", - "dev": true - }, - "chokidar": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", - "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", + "node_modules/browserstack": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", + "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" + "dependencies": { + "https-proxy-agent": "^2.2.1" } }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "node_modules/browserstack-local": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.4.8.tgz", + "integrity": "sha512-s+mc3gTOJwELdLWi4qFVKtGwMbb5JWsR+JxKlMaJkRJxoZ0gg3WREgPxAN0bm6iU5+S4Bi0sz0oxBRZT8BiNsQ==", + "dev": true, + "dependencies": { + "https-proxy-agent": "^4.0.0", + "is-running": "^2.1.0", + "ps-tree": "=1.2.0", + "temp-fs": "^0.9.9" + } }, - "chrome-launcher": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", - "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "node_modules/browserstack/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "dev": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^1.0.5", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^0.5.3", - "rimraf": "^3.0.2" - }, "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" } }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true + "node_modules/browserstack/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "node_modules/browserstack/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" } }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "node_modules/browserstack/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "node_modules/browserstacktunnel-wrapper": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.4.tgz", + "integrity": "sha512-GCV599FUUxNOCFl3WgPnfc5dcqq9XTmMXoxWpqkvmk0R9TOIoqmjENNU6LY6DtgIL6WfBVbg/jmWtnM5K6UYSg==", "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" + "dependencies": { + "https-proxy-agent": "^2.2.1", + "unzipper": "^0.9.3" }, + "engines": { + "node": ">= 0.10.20" + } + }, + "node_modules/browserstacktunnel-wrapper/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" } }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "node_modules/browserstacktunnel-wrapper/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "requires": { - "restore-cursor": "^3.1.0" + "dependencies": { + "ms": "^2.1.1" } }, - "cli-spinners": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.4.0.tgz", - "integrity": "sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA==", - "dev": true + "node_modules/browserstacktunnel-wrapper/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "node_modules/browserstacktunnel-wrapper/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "engines": { + "node": "*" + } }, - "clone-buffer": { + "node_modules/buffer-equal": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, - "clone-response": { + "node_modules/buffer-indexof-polyfill": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", "dev": true, - "requires": { - "mimic-response": "^1.0.0" + "engines": { + "node": ">=0.10" } }, - "clone-stats": { + "node_modules/buffer-shims": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", "dev": true }, - "cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", "dev": true, - "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } + "engines": { + "node": ">=0.2.0" } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, - "collapse-white-space": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", - "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "node_modules/builtins": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-0.0.7.tgz", + "integrity": "sha1-NVIZzWzxjb58Acx/0tznZc/cVJo=", "dev": true }, - "collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/cac/-/cac-3.0.4.tgz", + "integrity": "sha1-bSTO7Dcu/lybeYgIvH9JtHJCpO8=", "dev": true, - "requires": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "dependencies": { + "camelcase-keys": "^3.0.0", + "chalk": "^1.1.3", + "indent-string": "^3.0.0", + "minimist": "^1.2.0", + "read-pkg-up": "^1.0.1", + "suffix": "^0.1.0", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=4" } }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "node_modules/cac/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "engines": { + "node": ">=0.10.0" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/cac/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true, - "requires": { - "color-name": "1.1.3" + "engines": { + "node": ">=0.10.0" } }, - "color-name": { + "node_modules/cac/node_modules/chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true + "node_modules/cac/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true + "node_modules/cac/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, - "requires": { - "delayed-stream": "~1.0.0" + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "comma-separated-tokens": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", - "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", - "dev": true + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "engines": { + "node": ">=10.6.0" + } }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true + "node_modules/cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "node_modules/cached-path-relative": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz", + "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==", "dev": true }, - "component-bind": { + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "dependencies": { + "callsites": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caller-path/node_modules/callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/callsite": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true, + "engines": { + "node": "*" + } }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true + "node_modules/camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "compress-commons": { + "node_modules/camelcase-keys": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-3.0.0.tgz", - "integrity": "sha512-FyDqr8TKX5/X0qo+aVfaZ+PVmNJHJeckFBlq8jZGSJOgnynhfifoyl24qaqdUdDIBe0EVTHByN6NAkqYvE/2Xg==", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-3.0.0.tgz", + "integrity": "sha1-/AxsNgNj9zd+N5O5oWvM8QcMHKQ=", "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^3.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^2.3.7" - }, "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } + "camelcase": "^3.0.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "node_modules/caniuse-lite": { + "version": "1.0.30001239", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001239.tgz", + "integrity": "sha512-cyBkXJDMeI4wthy8xJ2FvDU6+0dtcZSJW3voUF8+e9f1bBeuvyZfc3PNbkOETyhbR+dGCPzn9E7MA3iwzusOhQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" } }, - "concat-with-sourcemaps": { + "node_modules/ccount": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", + "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "dev": true, - "requires": { - "source-map": "^0.6.1" - }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" } }, - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "node_modules/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", "dev": true, - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" } }, - "connect-livereload": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", - "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", - "dev": true + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "dev": true, + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true + "node_modules/character-entities-html4": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "requires": { - "safe-buffer": "5.1.2" + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "continuable-cache": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", - "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true, - "requires": { - "safe-buffer": "~5.1.1" + "engines": { + "node": "*" } }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "node_modules/check-types": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", + "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", + "dev": true }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, - "copy-props": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "node_modules/chrome-launcher": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.14.0.tgz", + "integrity": "sha512-W//HpflaW6qBGrmuskup7g+XJZN6w03ko9QSIe5CtcTal2u0up5SeReK3Ll1Why4Ey8dPkv8XSodZyHPnGbVHQ==", "dev": true, - "requires": { - "each-props": "^1.3.0", - "is-plain-object": "^2.0.1" + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "engines": { + "node": ">=12.13.0" } }, - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" - }, - "core-js-compat": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", - "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", + "node_modules/chrome-launcher/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "requires": { - "browserslist": "^4.8.5", - "semver": "7.0.0" + "engines": { + "node": ">=10" }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "core-js-pure": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", - "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==" + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "node_modules/circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "deprecated": "CircularJSON is in maintenance only, flatted is its successor.", "dev": true }, - "coveralls": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.0.tgz", - "integrity": "sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==", + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, - "requires": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, - "requires": { - "buffer": "^5.1.0" + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "crc32-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", - "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" } }, - "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "node_modules/cli-spinners": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", + "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==", "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "engines": { + "node": ">=6" }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" + "engines": { + "node": ">= 10" } }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "criteo-direct-rsa-validate": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/criteo-direct-rsa-validate/-/criteo-direct-rsa-validate-1.1.0.tgz", - "integrity": "sha512-7gQ3zX+d+hS/vOxzLrZ4aRAceB7qNJ0VzaGNpcWjDCmtOpASB50USJDupTik/H2nHgiSAA3VNZ3SFuONs8LR9Q==" + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true, + "engines": { + "node": ">=0.8" + } }, - "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" + "engines": { + "node": ">= 0.10" } }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" + "dependencies": { + "mimic-response": "^1.0.0" } }, - "crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true }, - "css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", "dev": true, - "requires": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" } }, - "css-value": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", - "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=", - "dev": true - }, - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "cssstyle": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", - "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "node_modules/cloneable-readable/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, - "requires": { - "cssom": "0.3.x" + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "node_modules/cloneable-readable/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "requires": { - "array-find-index": "^1.0.1" + "dependencies": { + "safe-buffer": "~5.1.0" } }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "engines": { + "node": ">=0.10.0" } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "node_modules/collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", "dev": true, - "requires": { - "assert-plus": "^1.0.0" + "dependencies": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } + "color-name": "1.1.3" } }, - "date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" + "bin": { + "color-support": "bin.js" } }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" + "node_modules/colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" } }, - "debug-fabulous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", - "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "node_modules/combine-source-map": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.3.0.tgz", + "integrity": "sha1-2edPWT2c1DgHMSy12EbUUe+qnrc=", "dev": true, - "requires": { - "debug": "3.X", - "memoizee": "0.4.X", - "object-assign": "4.X" - }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "convert-source-map": "~0.3.0", + "inline-source-map": "~0.3.0", + "source-map": "~0.1.31" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "node_modules/combine-source-map/node_modules/convert-source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", + "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=", "dev": true }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "node_modules/combine-source-map/node_modules/source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", "dev": true, - "requires": { - "mimic-response": "^3.1.0" - }, "dependencies": { - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true - } + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" } }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, - "requires": { - "type-detect": "^4.0.0" + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "deep-equal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz", - "integrity": "sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==", + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", "dev": true, - "requires": { - "es-abstract": "^1.17.5", - "es-get-iterator": "^1.1.0", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.0.5", - "isarray": "^2.0.5", - "object-is": "^1.1.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" - }, - "dependencies": { - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - } + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, - "requires": { - "kind-of": "^5.0.2" - }, "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" } }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/compress-commons": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", "dev": true, - "requires": { - "strip-bom": "^2.0.0" + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" } }, - "default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, - "optional": true, - "requires": { - "clone": "^1.0.2" + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, - "defer-to-connect": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz", - "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==", - "dev": true + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "requires": { - "object-keys": "^1.0.12" + "dependencies": { + "safe-buffer": "~5.1.0" } }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "node_modules/concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "source-map": "^0.6.1" } }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true + "node_modules/concat-with-sourcemaps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "node_modules/connect-livereload": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", + "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "engines": { + "node": "*" } }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true }, - "detab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.3.tgz", - "integrity": "sha512-Up8P0clUVwq0FnFjDclzZsy9PadzRn5FFxrr47tQQvMHqyiFYVbpH8oXDzWtF0Q7pYy3l+RPmtBl+BsFF6wH0A==", + "node_modules/consolidate": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.16.0.tgz", + "integrity": "sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==", "dev": true, - "requires": { - "repeat-string": "^1.5.4" + "optional": true, + "dependencies": { + "bluebird": "^3.7.2" + }, + "engines": { + "node": ">= 0.10.0" } }, - "detect-file": { + "node_modules/constants-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", "dev": true }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" } }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "dev": true + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "node_modules/continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", "dev": true }, - "detective": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", - "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", + "node_modules/conventional-changelog": { + "version": "3.1.24", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.24.tgz", + "integrity": "sha512-ed6k8PO00UVvhExYohroVPXcOJ/K1N0/drJHx/faTH37OIZthlecuLIRX/T6uOp682CAoVoFpu+sSEaeuH6Asg==", "dev": true, - "requires": { - "acorn": "^5.2.1", - "defined": "^1.0.0" + "dependencies": { + "conventional-changelog-angular": "^5.0.12", + "conventional-changelog-atom": "^2.0.8", + "conventional-changelog-codemirror": "^2.0.8", + "conventional-changelog-conventionalcommits": "^4.5.0", + "conventional-changelog-core": "^4.2.1", + "conventional-changelog-ember": "^2.0.9", + "conventional-changelog-eslint": "^3.0.9", + "conventional-changelog-express": "^2.0.6", + "conventional-changelog-jquery": "^3.0.11", + "conventional-changelog-jshint": "^2.0.9", + "conventional-changelog-preset-loader": "^2.3.4" + }, + "engines": { + "node": ">=10" } }, - "devtools": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.3.4.tgz", - "integrity": "sha512-dOcLdArp5/dJBzD8T5wcT2YgqkA22Mkqo0OS9cXz7JkHNgwOx1FI2Bq9GvP6o0TENHifYSYg3G0K/z0bacekqg==", + "node_modules/conventional-changelog-angular": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz", + "integrity": "sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw==", "dev": true, - "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/protocols": "6.3.0", - "@wdio/utils": "6.3.0", - "chrome-launcher": "^0.13.1", - "puppeteer-core": "^5.1.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" + "dependencies": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" } }, - "devtools-protocol": { - "version": "0.0.781568", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.781568.tgz", - "integrity": "sha512-9Uqnzy6m6zEStluH9iyJ3iHyaQziFnMnLeC8vK0eN6smiJmIx7+yB64d67C2lH/LZra+5cGscJAJsNXO+MdPMg==", - "dev": true - }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true + "node_modules/conventional-changelog-atom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", + "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "node_modules/conventional-changelog-codemirror": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", + "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } }, - "diff-sequences": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.0.0.tgz", - "integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==", + "node_modules/conventional-changelog-config-spec": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz", + "integrity": "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==", "dev": true }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "node_modules/conventional-changelog-conventionalcommits": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.5.0.tgz", + "integrity": "sha512-buge9xDvjjOxJlyxUnar/+6i/aVEVGA7EEh4OafBCXPlLUQPGbRUBhBUveWRxzvR8TEjhKEP4BdepnpG2FSZXw==", "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } + "compare-func": "^2.0.0", + "lodash": "^4.17.15", + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" } }, - "disparity": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/disparity/-/disparity-2.0.0.tgz", - "integrity": "sha1-V92stHMkrl9Y0swNqIbbTOnutxg=", - "dev": true, - "requires": { - "ansi-styles": "^2.0.1", - "diff": "^1.3.2" + "node_modules/conventional-changelog-core": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.2.tgz", + "integrity": "sha512-7pDpRUiobQDNkwHyJG7k9f6maPo9tfPzkSWbRq97GGiZqisElhnvUZSvyQH20ogfOjntB5aadvv6NNcKL1sReg==", + "dev": true, + "dependencies": { + "add-stream": "^1.0.0", + "conventional-changelog-writer": "^4.0.18", + "conventional-commits-parser": "^3.2.0", + "dateformat": "^3.0.0", + "get-pkg-repo": "^1.0.0", + "git-raw-commits": "^2.0.8", + "git-remote-origin-url": "^2.0.0", + "git-semver-tags": "^4.1.1", + "lodash": "^4.17.15", + "normalize-package-data": "^3.0.0", + "q": "^1.5.1", + "read-pkg": "^3.0.0", + "read-pkg-up": "^3.0.0", + "shelljs": "^0.8.3", + "through2": "^4.0.0" }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "diff": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", - "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", - "dev": true - } + "engines": { + "node": ">=10" } }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "node_modules/conventional-changelog-core/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "doctrine-temporary-fork": { - "version": "2.0.0-alpha-allowarrayindex", - "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.0.0-alpha-allowarrayindex.tgz", - "integrity": "sha1-QAFahn6yfnWybIKLcVJPE3+J+fA=", + "node_modules/conventional-changelog-core/node_modules/hosted-git-info": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" } }, - "documentation": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-5.5.0.tgz", - "integrity": "sha512-Aod3HOI+8zMhwWztDlECRsDfJ8SFu4oADvipOLq3gnWKy4Cpg2oF5AWT+U6PcX85KuguDI6c+q+2YwYEx99B/A==", + "node_modules/conventional-changelog-core/node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, - "requires": { - "ansi-html": "^0.0.7", - "babel-core": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-plugin-system-import-transformer": "3.1.0", - "babel-plugin-transform-decorators-legacy": "^1.3.4", - "babel-preset-env": "^1.6.1", - "babel-preset-react": "^6.24.1", - "babel-preset-stage-0": "^6.24.1", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babelify": "^8.0.0", - "babylon": "^6.18.0", - "chalk": "^2.3.0", - "chokidar": "^2.0.0", - "concat-stream": "^1.6.0", - "disparity": "^2.0.0", - "doctrine-temporary-fork": "2.0.0-alpha-allowarrayindex", - "get-port": "^3.2.0", - "git-url-parse": "^8.0.0", - "github-slugger": "1.2.0", - "glob": "^7.1.2", - "globals-docs": "^2.4.0", - "highlight.js": "^9.12.0", - "js-yaml": "^3.10.0", - "lodash": "^4.17.4", - "mdast-util-inject": "^1.1.0", - "micromatch": "^3.1.5", - "mime": "^1.4.1", - "module-deps-sortable": "4.0.6", - "parse-filepath": "^1.0.2", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", "pify": "^3.0.0", - "read-pkg-up": "^3.0.0", - "remark": "^9.0.0", - "remark-html": "7.0.0", - "remark-reference-links": "^4.0.1", - "remark-toc": "^5.0.0", - "remote-origin-url": "0.4.0", - "shelljs": "^0.8.1", - "stream-array": "^1.1.2", - "strip-json-comments": "^2.0.1", - "tiny-lr": "^1.1.0", - "unist-builder": "^1.0.2", - "unist-util-visit": "^1.3.0", - "vfile": "^2.3.0", - "vfile-reporter": "^4.0.0", - "vfile-sort": "^2.1.0", - "vinyl": "^2.1.0", - "vinyl-fs": "^3.0.2", - "yargs": "^9.0.1" + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-core/node_modules/normalize-package-data": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", + "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-core/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/conventional-changelog-core/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-core/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/conventional-changelog-ember": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", + "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-eslint": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", + "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-express": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", + "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-jquery": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", + "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-jshint": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", + "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-preset-loader": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", + "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz", + "integrity": "sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0", + "conventional-commits-filter": "^2.0.7", + "dateformat": "^3.0.0", + "handlebars": "^4.7.6", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^4.0.0" + }, + "bin": { + "conventional-changelog-writer": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer/node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/conventional-commits-filter": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", + "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "dev": true, + "dependencies": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-commits-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.1.tgz", + "integrity": "sha512-OG9kQtmMZBJD/32NEw5IhN5+HnBqVjy03eC+I71I0oQRFA5rOgA4OtPOYG7mz1GkCfCNxn3gKIX8EiHJYuf1cA==", + "dev": true, + "dependencies": { + "is-text-path": "^1.0.1", + "JSONStream": "^1.0.4", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0", + "trim-off-newlines": "^1.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-recommended-bump": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz", + "integrity": "sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==", + "dev": true, + "dependencies": { + "concat-stream": "^2.0.0", + "conventional-changelog-preset-loader": "^2.3.4", + "conventional-commits-filter": "^2.0.7", + "conventional-commits-parser": "^3.2.0", + "git-raw-commits": "^2.0.8", + "git-semver-tags": "^4.1.1", + "meow": "^8.0.0", + "q": "^1.5.1" + }, + "bin": { + "conventional-recommended-bump": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-recommended-bump/node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "dev": true, + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-props": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", + "dev": true, + "dependencies": { + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" + } + }, + "node_modules/copy-props/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core-js": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.15.1.tgz", + "integrity": "sha512-h8VbZYnc9pDzueiS2610IULDkpFFPunHwIpl8yRwFahAEEdSpHlTy3h3z3rKq5h11CaUdBEeRViu9AYvbxiMeg==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.1.tgz", + "integrity": "sha512-xGhzYMX6y7oEGQGAJmP2TmtBLvR4nZmRGEcFa3ubHOq5YEp51gGN9AovVa0AoujGZIq+Wm6dISiYyGNfdflYww==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "semver": "7.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/core-js-pure": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.15.1.tgz", + "integrity": "sha512-OZuWHDlYcIda8sJLY4Ec6nWq2hRjlyCqCZ+jCflyleMkVt3tPedDVErvHslyS2nbO+SlBFMSBJYvtLMwxnrzjA==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/coveralls": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.0.tgz", + "integrity": "sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.2" + }, + "bin": { + "coveralls": "bin/coveralls.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/coveralls/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/coveralls/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "dev": true, + "dependencies": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + }, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "dev": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/criteo-direct-rsa-validate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/criteo-direct-rsa-validate/-/criteo-direct-rsa-validate-1.1.0.tgz", + "integrity": "sha512-7gQ3zX+d+hS/vOxzLrZ4aRAceB7qNJ0VzaGNpcWjDCmtOpASB50USJDupTik/H2nHgiSAA3VNZ3SFuONs8LR9Q==" + }, + "node_modules/cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/cryptiles": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", + "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dev": true, + "optional": true, + "dependencies": { + "boom": "0.4.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/crypto-js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + }, + "node_modules/css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, + "node_modules/css-shorthand-properties": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", + "integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==", + "dev": true + }, + "node_modules/css-value": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", + "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=", + "dev": true + }, + "node_modules/css/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css/node_modules/source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, + "node_modules/cssauron": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", + "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", + "dev": true, + "dependencies": { + "through": "X.X.X" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "optional": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "dependencies": { + "array-find-index": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/dargs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/date-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", + "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", + "dev": true, + "optional": true + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "dev": true, + "dependencies": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + } + }, + "node_modules/debug-fabulous/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/debug-fabulous/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/deep-equal": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", + "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "es-get-iterator": "^1.1.1", + "get-intrinsic": "^1.0.1", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.1.1", + "isarray": "^2.0.5", + "object-is": "^1.1.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.3", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "dependencies": { + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-compare/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/deps-sort": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-1.3.9.tgz", + "integrity": "sha1-Kd//U+F7Nq7K51MK27v2IsLtGnE=", + "dev": true, + "dependencies": { + "JSONStream": "^1.0.3", + "shasum": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^1.0.0" + }, + "bin": { + "deps-sort": "bin/cmd.js" + } + }, + "node_modules/deps-sort/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/deps-sort/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/deps-sort/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/deps-sort/node_modules/through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "dependencies": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detective": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", + "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "dev": true, + "dependencies": { + "acorn-node": "^1.6.1", + "defined": "^1.0.0", + "minimist": "^1.1.1" + }, + "bin": { + "detective": "bin/detective.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/devtools": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.7.4.tgz", + "integrity": "sha512-rkO9k6yOA2XzFTph9y+gO/387653jou0La7QSLd57XTQiM3D/UODqLBt+fMVu8w3fdQzZHVAlIIvP4B8rkXY1Q==", + "dev": true, + "dependencies": { + "@types/node": "^14.14.31", + "@wdio/config": "7.7.3", + "@wdio/logger": "7.7.0", + "@wdio/protocols": "7.7.4", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", + "chrome-launcher": "^0.14.0", + "edge-paths": "^2.1.0", + "puppeteer-core": "^9.1.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^0.7.21", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.892017", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.892017.tgz", + "integrity": "sha512-23yn1+zeMBlWiZTtrCViNQt+W+FRDw5rEetI19bMuyKIYeK11xo/dS+Hmuu8ifGJnJvXUU3Y79IoxSPWZWcVOA==", + "dev": true + }, + "node_modules/devtools/node_modules/@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true + }, + "node_modules/devtools/node_modules/@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/devtools/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/devtools/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/devtools/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/devtools/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/devtools/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/devtools/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", + "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.1.tgz", + "integrity": "sha512-XPLijkfJUh/PIBnfkcSHgvD6tlYixmcMAn3osTk6jt+H0v/mgURto1XUiD9DKuGX5NDoVS6dSlA23gd9FUaCFg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/doctrine-temporary-fork": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz", + "integrity": "sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation": { + "version": "13.2.5", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-13.2.5.tgz", + "integrity": "sha512-d1TrfrHXYZR63xrOzkYwwe297vkSwBoEhyyMBOi20T+7Ohe1aX1dW4nqXncQmdmE5MxluSaxxa3BW1dCvbF5AQ==", + "dev": true, + "dependencies": { + "@babel/core": "7.12.3", + "@babel/generator": "7.12.1", + "@babel/parser": "7.12.3", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "ansi-html": "^0.0.7", + "babelify": "^10.0.0", + "chalk": "^2.3.0", + "chokidar": "^3.4.0", + "concat-stream": "^1.6.0", + "diff": "^4.0.1", + "doctrine-temporary-fork": "2.1.0", + "get-port": "^5.0.0", + "git-url-parse": "^11.1.2", + "github-slugger": "1.2.0", + "glob": "^7.1.2", + "globals-docs": "^2.4.0", + "highlight.js": "^10.7.2", + "ini": "^1.3.5", + "js-yaml": "^3.10.0", + "lodash": "^4.17.10", + "mdast-util-find-and-replace": "^1.1.1", + "mdast-util-inject": "^1.1.0", + "micromatch": "^3.1.5", + "mime": "^2.2.0", + "module-deps-sortable": "^5.0.3", + "parse-filepath": "^1.0.2", + "pify": "^5.0.0", + "read-pkg-up": "^4.0.0", + "remark": "^13.0.0", + "remark-gfm": "^1.0.0", + "remark-html": "^13.0.1", + "remark-reference-links": "^5.0.0", + "remark-toc": "^7.2.0", + "resolve": "^1.8.1", + "stream-array": "^1.1.2", + "strip-json-comments": "^2.0.1", + "tiny-lr": "^1.1.0", + "unist-builder": "^2.0.3", + "unist-util-visit": "^2.0.3", + "vfile": "^4.0.0", + "vfile-reporter": "^6.0.0", + "vfile-sort": "^2.1.0", + "vinyl": "^2.1.0", + "vinyl-fs": "^3.0.2", + "yargs": "^15.3.1" + }, + "bin": { + "documentation": "bin/documentation.js" + }, + "engines": { + "node": ">=10" + }, + "optionalDependencies": { + "@vue/compiler-sfc": "^3.0.11", + "vue-template-compiler": "^2.6.12" + } + }, + "node_modules/documentation/node_modules/@babel/core": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", + "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.1", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.1", + "@babel/parser": "^7.12.3", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/documentation/node_modules/@babel/generator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", + "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.1", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "node_modules/documentation/node_modules/@babel/parser": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", + "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/documentation/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/documentation/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/documentation/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/documentation/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/documentation/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/documentation/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/documentation/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/documentation/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/documentation/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/documentation/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/documentation/node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/documentation/node_modules/load-json-file/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/documentation/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/documentation/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/documentation/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/documentation/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/documentation/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/documentation/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/documentation/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/documentation/node_modules/path-type/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/documentation/node_modules/pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/documentation/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/documentation/node_modules/read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/documentation/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/documentation/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/documentation/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/documentation/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/documentation/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/documentation/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/documentation/node_modules/yargs/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/documentation/node_modules/yargs/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/documentation/node_modules/yargs/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/documentation/node_modules/yargs/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true, + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotgitignore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", + "integrity": "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/dset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dset/-/dset-2.0.1.tgz", + "integrity": "sha512-nI29OZMRYq36hOcifB6HTjajNAAiBKSXsyWZrq+VniusseuP2OpNlTiYgsaNRSGvpyq5Wjbc2gQLyBdTyWqhnQ==", + "deprecated": "Please use dset@2.1.0 or later for an important security patch", + "engines": { + "node": ">=4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexify/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "node_modules/easy-table": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.1.tgz", + "integrity": "sha512-C9Lvm0WFcn2RgxbMnTbXZenMIWcBtkzMr+dWqq/JsVoGFSVUVlPqeOa5LP5kM0I3zoOazFpckOEb2/0LDFfToQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "optionalDependencies": { + "wcwidth": ">=1.0.1" + } + }, + "node_modules/easy-table/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/edge-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", + "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", + "dev": true, + "dependencies": { + "@types/which": "^1.3.2", + "which": "^2.0.2" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/ejs": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", + "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", + "dev": true, + "dependencies": { + "jake": "^10.6.1" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.3.755", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.755.tgz", + "integrity": "sha512-BJ1s/kuUuOeo1bF/EM2E4yqW9te0Hpof3wgwBx40AWJE18zsD1Tqo0kr7ijnOc+lRsrlrqKPauJAHqaxOItoUA==", + "dev": true + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/elliptic/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.1.tgz", + "integrity": "sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.0", + "ws": "~7.4.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", + "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", + "dev": true, + "dependencies": { + "base64-arraybuffer": "0.1.4" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/engine.io/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/enhanced-resolve": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", + "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "object-assign": "^4.0.1", + "tapable": "^0.2.7" + }, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "node_modules/entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "dev": true + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", + "dev": true, + "dependencies": { + "string-template": "~0.2.1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.10.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", + "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.0", + "has-symbols": "^1.0.1", + "is-arguments": "^1.1.0", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "dependencies": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "node_modules/es5-shim": { + "version": "4.5.15", + "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.5.15.tgz", + "integrity": "sha512-FYpuxEjMeDvU4rulKqFdukQyZSTpzhg4ScQHrAosrlVpR6GFyaw14f74yn2+4BugniIS0Frpg7TvwZocU4ZMTw==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + } + }, + "node_modules/es6-set/node_modules/es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "dependencies": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=0.12.0" + }, + "optionalDependencies": { + "source-map": "~0.2.0" + } + }, + "node_modules/escodegen/node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "dependencies": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/eslint": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-standard": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", + "integrity": "sha1-wGHk0GbzedwXzVYsZOgZtN1FRZE=", + "dev": true, + "peerDependencies": { + "eslint": ">=3.19.0", + "eslint-plugin-import": ">=2.2.0", + "eslint-plugin-node": ">=4.2.2", + "eslint-plugin-promise": ">=3.5.0", + "eslint-plugin-standard": ">=3.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "dev": true, + "dependencies": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.1.tgz", + "integrity": "sha512-ZXI9B8cxAJIH4nfkhTwcRTEAnrVfobYqwjWy/QMCZ8rHkZHFjf9yO4BzpiF9kCSfNlMG54eKigISHpX0+AaT4A==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "pkg-dir": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/eslint-module-utils/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "dependencies": { + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.23.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz", + "integrity": "sha512-6/wP8zZRsnQFiR3iaPFgh5ImVRM1WN5NUWfTIRqwOdeiGJlBcSk82o1FEVq8yXmy4lkIzTo7YhHCIxlU/2HyEQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.3", + "array.prototype.flat": "^1.2.4", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-module-utils": "^2.6.1", + "find-up": "^2.0.0", + "has": "^1.0.3", + "is-core-module": "^2.4.0", + "minimatch": "^3.0.4", + "object.values": "^1.1.3", + "pkg-up": "^2.0.0", + "read-pkg-up": "^3.0.0", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.9.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint-plugin-prebid": { + "resolved": "plugins/eslint", + "link": true + }, + "node_modules/eslint-plugin-promise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz", + "integrity": "sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==", + "dev": true, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0" + } + }, + "node_modules/eslint-plugin-standard": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz", + "integrity": "sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w==", + "dev": true, + "peerDependencies": { + "eslint": ">=3.19.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima-fb": { + "version": "10001.1.0-dev-harmony-fb", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-10001.1.0-dev-harmony-fb.tgz", + "integrity": "sha1-9++0UtPIAG3eazxZZ4YE9xFKiCw=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse-fb": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/estraverse-fb/-/estraverse-fb-1.3.2.tgz", + "integrity": "sha1-0yOky15awzHOoDNBOpJT4WQ+B8Q=", + "dev": true, + "peerDependencies": { + "estraverse": "*" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "optional": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "dependencies": { + "fill-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "dependencies": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.0.2.tgz", + "integrity": "sha512-YJFNJe2+P2DqH+ZrXy+ydRQYO87oxRUonZImpDodR1G7qo3NYd3pL+NQ9Keqpez3cehczYwZDBC3A7xk3n7M/w==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.2", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.0.1", + "jest-matcher-utils": "^27.0.2", + "jest-message-util": "^27.0.2", + "jest-regex-util": "^27.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/expect-webdriverio": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-3.1.0.tgz", + "integrity": "sha512-Kn4Rtu5vKbDo95WNcjZ9XVz/qTPGZzumP9w7VSV4OxY5z6BAqSI2jb85EsqPxpavs33P+9Qse4Z+d5ilDD/dQw==", + "dev": true, + "dependencies": { + "expect": "^27.0.2", + "jest-matcher-utils": "^27.0.2" + } + }, + "node_modules/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "dependencies": { + "type": "^2.0.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==", + "dev": true + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/extract-zip/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==", + "dev": true + }, + "node_modules/falafel": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/falafel/-/falafel-0.1.6.tgz", + "integrity": "sha1-MITPPUG1nRXIE75vJZVX/cgrBmA=", + "dev": true, + "dependencies": { + "esprima": "~1.0.2" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/falafel/node_modules/esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "dependencies": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fibers": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.0.tgz", + "integrity": "sha512-UpGv/YAZp7mhKHxDvC1tColrroGRX90sSvh8RMZV9leo+e5+EkRVgCEZPlmXeo3BUNQTZxUaVdLskq1Q2FyCPg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "node_modules/filelist": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", + "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fileset": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.2.1.tgz", + "integrity": "sha1-WI74lzxmI7KnbfRlEFaWuWqsgGc=", + "dev": true, + "dependencies": { + "glob": "5.x", + "minimatch": "2.x" + } + }, + "node_modules/fileset/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fileset/node_modules/minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "dependencies": { + "brace-expansion": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", + "dev": true, + "dependencies": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/findup-sync/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "dev": true + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/flush-write-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/flush-write-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, + "node_modules/foreachasync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", + "integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY=", + "dev": true + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/fork-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", + "integrity": "sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=", + "dev": true + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/formatio": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", + "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", + "deprecated": "This package is unmaintained. Use @sinonjs/formatio instead", + "dev": true, + "dependencies": { + "samsam": "~1.1" + } + }, + "node_modules/formatio/node_modules/samsam": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.3.tgz", + "integrity": "sha1-n1CHQZtNCR8jJXHn+lLpCw9VJiE=", + "deprecated": "This package has been deprecated in favour of @sinonjs/samsam", + "dev": true + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, + "node_modules/fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "dev": true, + "dependencies": { + "null-check": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fs-mkdirp-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/fs-mkdirp-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fs-mkdirp-stream/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/fs.extra": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz", + "integrity": "sha1-3QI/kwE77iRTHxszUUw3sg/ZM0k=", + "dev": true, + "dependencies": { + "fs-extra": "~0.6.1", + "mkdirp": "~0.3.5", + "walk": "^2.3.9" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fs.extra/node_modules/fs-extra": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", + "integrity": "sha1-9G8MdbeEH40gCzNIzU1pHVoJnRU=", + "dev": true, + "dependencies": { + "jsonfile": "~1.0.1", + "mkdirp": "0.3.x", + "ncp": "~0.4.2", + "rimraf": "~2.2.0" + } + }, + "node_modules/fs.extra/node_modules/jsonfile": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", + "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=", + "dev": true + }, + "node_modules/fs.extra/node_modules/mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true + }, + "node_modules/fs.extra/node_modules/rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/fun-hooks": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/fun-hooks/-/fun-hooks-0.9.10.tgz", + "integrity": "sha512-7xBjdT+oMYOPWgwFxNiNzF4ubeUvim4zs1DnQqSSGyxu8UD7AW/6Z0iFsVRwuVSIZKUks2en2VHHotmNfj3ipw==", + "dependencies": { + "typescript-tuple": "^2.2.1" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "dependencies": { + "globule": "^1.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "dependencies": { + "is-property": "^1.0.0" + } + }, + "node_modules/generic-names": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz", + "integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==", + "dev": true, + "optional": true, + "dependencies": { + "loader-utils": "^1.1.0" + } + }, + "node_modules/generic-names/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "optional": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/generic-names/node_modules/loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "optional": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-pkg-repo": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz", + "integrity": "sha1-xztInAbYDMVTbCyFP54FIyBWly0=", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "meow": "^3.3.0", + "normalize-package-data": "^2.3.0", + "parse-github-repo-url": "^1.3.0", + "through2": "^2.0.0" + }, + "bin": { + "get-pkg-repo": "cli.js" + } + }, + "node_modules/get-pkg-repo/node_modules/camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-pkg-repo/node_modules/camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "dependencies": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-pkg-repo/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-pkg-repo/node_modules/indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "dependencies": { + "repeating": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-pkg-repo/node_modules/meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "dependencies": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-pkg-repo/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/get-pkg-repo/node_modules/redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "dependencies": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-pkg-repo/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/get-pkg-repo/node_modules/strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "dependencies": { + "get-stdin": "^4.0.1" + }, + "bin": { + "strip-indent": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-pkg-repo/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/get-pkg-repo/node_modules/trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/gh-got": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-1.1.0.tgz", + "integrity": "sha1-UeIHrxkRPlBjM3I3zrMyhGmaSzs=", + "dev": true, + "dependencies": { + "got": "^3.2.0", + "object-assign": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-got/node_modules/got": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz", + "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=", + "dev": true, + "dependencies": { + "duplexify": "^3.2.0", + "infinity-agent": "^2.0.0", + "is-redirect": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "nested-error-stacks": "^1.0.0", + "object-assign": "^3.0.0", + "prepend-http": "^1.0.0", + "read-all-stream": "^3.0.0", + "timed-out": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-got/node_modules/got/node_modules/object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-got/node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-got/node_modules/object-assign": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", + "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/git-raw-commits": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.10.tgz", + "integrity": "sha512-sHhX5lsbG9SOO6yXdlwgEMQ/ljIn7qMpAbJZCGfXX2fq5T8M5SrDnpYk9/4HswTildcIqatsWa91vty6VhWSaQ==", + "dev": true, + "dependencies": { + "dargs": "^7.0.0", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/git-remote-origin-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", + "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", + "dev": true, + "dependencies": { + "gitconfiglocal": "^1.0.0", + "pify": "^2.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", + "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", + "dev": true, + "dependencies": { + "meow": "^8.0.0", + "semver": "^6.0.0" + }, + "bin": { + "git-semver-tags": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/git-up": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-4.0.2.tgz", + "integrity": "sha512-kbuvus1dWQB2sSW4cbfTeGpCMd8ge9jx9RKnhXhuJ7tnvT+NIrTVfYZxjtflZddQYcmdOTlkAcjmx7bor+15AQ==", + "dev": true, + "dependencies": { + "is-ssh": "^1.3.0", + "parse-url": "^5.0.0" + } + }, + "node_modules/git-url-parse": { + "version": "11.4.4", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.4.4.tgz", + "integrity": "sha512-Y4o9o7vQngQDIU9IjyCmRJBin5iYjI5u9ZITnddRZpD7dcCFQj2sL2XuMNbLRE4b4B/4ENPsp2Q8P44fjAZ0Pw==", + "dev": true, + "dependencies": { + "git-up": "^4.0.0" + } + }, + "node_modules/gitconfiglocal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", + "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", + "dev": true, + "dependencies": { + "ini": "^1.3.2" + } + }, + "node_modules/github-slugger": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.2.0.tgz", + "integrity": "sha512-wIaa75k1vZhyPm9yWrD08A5Xnx/V+RmzGrpjQuLemGKSb77Qukiaei58Bogrl/LZSADDfPzKJX8jhLs4CRTl7Q==", + "dev": true, + "dependencies": { + "emoji-regex": ">=6.0.0 <=6.1.1" + } + }, + "node_modules/github-slugger/node_modules/emoji-regex": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", + "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=", + "dev": true + }, + "node_modules/github-url-from-git": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-url-from-git/-/github-url-from-git-1.5.0.tgz", + "integrity": "sha1-+YX+3MCpqledyI16/waNVcxiUaA=", + "dev": true + }, + "node_modules/github-url-from-username-repo": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/github-url-from-username-repo/-/github-url-from-username-repo-1.0.2.tgz", + "integrity": "sha1-fdeTMNKr5pwQws73lxTJchV5Hfo=", + "dev": true + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "dependencies": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-base/node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/glob-base/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-base/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "dependencies": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/glob-stream/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-stream/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/glob-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/glob-watcher/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/glob-watcher/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/glob-watcher/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/glob-watcher/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-watcher/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/glob-watcher/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/glob-watcher/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/glob-watcher/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globals-docs": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/globals-docs/-/globals-docs-2.4.1.tgz", + "integrity": "sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==", + "dev": true + }, + "node_modules/globule": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", + "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", + "dev": true, + "dependencies": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dev": true, + "dependencies": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-clean": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/gulp-clean/-/gulp-clean-0.3.2.tgz", + "integrity": "sha1-o0fUc6zqQBgvk1WHpFGUFnGSgQI=", + "dev": true, + "dependencies": { + "gulp-util": "^2.2.14", + "rimraf": "^2.2.8", + "through2": "^0.4.2" + }, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/gulp-clean/node_modules/ansi-regex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/ansi-styles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "dependencies": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/chalk": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "dev": true, + "dependencies": { + "ansi-styles": "^1.1.0", + "escape-string-regexp": "^1.0.0", + "has-ansi": "^0.1.0", + "strip-ansi": "^0.3.0", + "supports-color": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "node_modules/gulp-clean/node_modules/dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "dependencies": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + }, + "bin": { + "dateformat": "bin/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gulp-clean/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/gulp-util": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", + "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", + "deprecated": "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5", + "dev": true, + "dependencies": { + "chalk": "^0.5.0", + "dateformat": "^1.0.7-1.2.3", + "lodash._reinterpolate": "^2.4.1", + "lodash.template": "^2.4.1", + "minimist": "^0.2.0", + "multipipe": "^0.1.0", + "through2": "^0.5.0", + "vinyl": "^0.2.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/gulp-clean/node_modules/gulp-util/node_modules/through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "dependencies": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + }, + "node_modules/gulp-clean/node_modules/has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "dev": true, + "dependencies": { + "ansi-regex": "^0.2.0" + }, + "bin": { + "has-ansi": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "dependencies": { + "repeating": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/gulp-clean/node_modules/meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "dependencies": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/meow/node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/gulp-clean/node_modules/minimist": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.1.tgz", + "integrity": "sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==", + "dev": true + }, + "node_modules/gulp-clean/node_modules/object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "node_modules/gulp-clean/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/gulp-clean/node_modules/redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "dependencies": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/gulp-clean/node_modules/strip-ansi": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "dev": true, + "dependencies": { + "ansi-regex": "^0.2.1" + }, + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "dependencies": { + "get-stdin": "^4.0.1" + }, + "bin": { + "strip-indent": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true, + "bin": { + "supports-color": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/through2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "dev": true, + "dependencies": { + "readable-stream": "~1.0.17", + "xtend": "~2.1.1" + } + }, + "node_modules/gulp-clean/node_modules/through2/node_modules/xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "dependencies": { + "object-keys": "~0.4.0" + }, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/gulp-clean/node_modules/trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/vinyl": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", + "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + "dev": true, + "dependencies": { + "clone-stats": "~0.0.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/gulp-clean/node_modules/xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/gulp-concat": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", + "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", + "dev": true, + "dependencies": { + "concat-with-sourcemaps": "^1.0.0", + "through2": "^2.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-concat/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-concat/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-concat/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-connect": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/gulp-connect/-/gulp-connect-5.7.0.tgz", + "integrity": "sha512-8tRcC6wgXMLakpPw9M7GRJIhxkYdgZsXwn7n56BA2bQYGLR9NOPhMzx7js+qYDy6vhNkbApGKURjAw1FjY4pNA==", + "dev": true, + "dependencies": { + "ansi-colors": "^2.0.5", + "connect": "^3.6.6", + "connect-livereload": "^0.6.0", + "fancy-log": "^1.3.2", + "map-stream": "^0.0.7", + "send": "^0.16.2", + "serve-index": "^1.9.1", + "serve-static": "^1.13.2", + "tiny-lr": "^1.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-connect/node_modules/ansi-colors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-2.0.5.tgz", + "integrity": "sha512-yAdfUZ+c2wetVNIFsNRn44THW+Lty6S5TwMpUfLA/UaGhiXbBv/F8E60/1hMLd0cnF/CDoWH8vzVaI5bAcHCjw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-connect/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/gulp-connect/node_modules/map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, + "node_modules/gulp-connect/node_modules/mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true, + "bin": { + "mime": "cli.js" + } + }, + "node_modules/gulp-connect/node_modules/send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/gulp-connect/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/gulp-connect/node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/gulp-eslint": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-4.0.2.tgz", + "integrity": "sha512-fcFUQzFsN6dJ6KZlG+qPOEkqfcevRUXgztkYCvhNvJeSvOicC8ucutN4qR/ID8LmNZx9YPIkBzazTNnVvbh8wg==", + "dev": true, + "dependencies": { + "eslint": "^4.0.0", + "fancy-log": "^1.3.2", + "plugin-error": "^1.0.0" + } + }, + "node_modules/gulp-eslint/node_modules/acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/gulp-eslint/node_modules/acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "dependencies": { + "acorn": "^3.0.4" + } + }, + "node_modules/gulp-eslint/node_modules/acorn-jsx/node_modules/acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/gulp-eslint/node_modules/ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true, + "peerDependencies": { + "ajv": "^5.0.0" + } + }, + "node_modules/gulp-eslint/node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gulp-eslint/node_modules/chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "node_modules/gulp-eslint/node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "node_modules/gulp-eslint/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/gulp-eslint/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/gulp-eslint/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-eslint/node_modules/eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "dev": true, + "dependencies": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/gulp-eslint/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "dependencies": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-eslint/node_modules/external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "dependencies": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/gulp-eslint/node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "dependencies": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-eslint/node_modules/flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "dependencies": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-eslint/node_modules/ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "node_modules/gulp-eslint/node_modules/inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + }, + "node_modules/gulp-eslint/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/gulp-eslint/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/gulp-eslint/node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/gulp-eslint/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/gulp-eslint/node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "node_modules/gulp-eslint/node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/gulp-eslint/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/gulp-eslint/node_modules/regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/gulp-eslint/node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/gulp-eslint/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/gulp-eslint/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-eslint/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-eslint/node_modules/slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-eslint/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-eslint/node_modules/table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "dependencies": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + }, + "node_modules/gulp-eslint/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/gulp-eslint/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/gulp-footer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/gulp-footer/-/gulp-footer-2.0.2.tgz", + "integrity": "sha512-HsG5VOgKHFRqZXnHGI6oGhPDg70p9pobM+dYOnjBZVLMQUHzLG6bfaPNRJ7XG707E+vWO3TfN0CND9UrYhk94g==", + "dev": true, + "dependencies": { + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.6.2", + "map-stream": "0.0.7" + } + }, + "node_modules/gulp-footer/node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "node_modules/gulp-footer/node_modules/lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "dependencies": { + "lodash._root": "^3.0.0" + } + }, + "node_modules/gulp-footer/node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/gulp-footer/node_modules/lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "node_modules/gulp-footer/node_modules/lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "node_modules/gulp-footer/node_modules/map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, + "node_modules/gulp-header": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-2.0.9.tgz", + "integrity": "sha512-LMGiBx+qH8giwrOuuZXSGvswcIUh0OiioNkUpLhNyvaC6/Ga8X6cfAeme2L5PqsbXMhL8o8b/OmVqIQdxprhcQ==", + "dev": true, + "dependencies": { + "concat-with-sourcemaps": "^1.1.0", + "lodash.template": "^4.5.0", + "map-stream": "0.0.7", + "through2": "^2.0.0" + } + }, + "node_modules/gulp-header/node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "node_modules/gulp-header/node_modules/lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "node_modules/gulp-header/node_modules/lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "node_modules/gulp-header/node_modules/map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, + "node_modules/gulp-header/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-header/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-header/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-if": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-3.0.0.tgz", + "integrity": "sha512-fCUEngzNiEZEK2YuPm+sdMpO6ukb8+/qzbGfJBXyNOXz85bCG7yBI+pPSl+N90d7gnLvMsarthsAImx0qy7BAw==", + "dev": true, + "dependencies": { + "gulp-match": "^1.1.0", + "ternary-stream": "^3.0.0", + "through2": "^3.0.1" + } + }, + "node_modules/gulp-if/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/gulp-if/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/gulp-js-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gulp-js-escape/-/gulp-js-escape-1.0.1.tgz", + "integrity": "sha1-HNRF+9AJ4Np2lZoDp/SbNWav+Gg=", + "dev": true, + "dependencies": { + "through2": "^0.6.3" + } + }, + "node_modules/gulp-js-escape/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/gulp-js-escape/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/gulp-js-escape/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/gulp-js-escape/node_modules/through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "dependencies": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/gulp-match": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.1.0.tgz", + "integrity": "sha512-DlyVxa1Gj24DitY2OjEsS+X6tDpretuxD6wTfhXE/Rw2hweqc1f6D/XtsJmoiCwLWfXgR87W9ozEityPCVzGtQ==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.3" + } + }, + "node_modules/gulp-replace": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", + "integrity": "sha512-HcPHpWY4XdF8zxYkDODHnG2+7a3nD/Y8Mfu3aBgMiCFDW3X2GiOKXllsAmILcxe3KZT2BXoN18WrpEFm48KfLQ==", + "dev": true, + "dependencies": { + "@types/node": "^14.14.41", + "@types/vinyl": "^2.0.4", + "istextorbinary": "^3.0.0", + "replacestream": "^4.0.3", + "yargs-parser": ">=5.0.0-security.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gulp-replace/node_modules/@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true + }, + "node_modules/gulp-shell": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/gulp-shell/-/gulp-shell-0.8.0.tgz", + "integrity": "sha512-wHNCgmqbWkk1c6Gc2dOL5SprcoeujQdeepICwfQRo91DIylTE7a794VEE+leq3cE2YDoiS5ulvRfKVIEMazcTQ==", + "dev": true, + "dependencies": { + "chalk": "^3.0.0", + "fancy-log": "^1.3.3", + "lodash.template": "^4.5.0", + "plugin-error": "^1.0.1", + "through2": "^3.0.1", + "tslib": "^1.10.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/gulp-shell/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/gulp-shell/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gulp-shell/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/gulp-shell/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/gulp-shell/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/gulp-shell/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/gulp-shell/node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "node_modules/gulp-shell/node_modules/lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "node_modules/gulp-shell/node_modules/lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "node_modules/gulp-shell/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gulp-shell/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/gulp-sourcemaps": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-3.0.0.tgz", + "integrity": "sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ==", + "dev": true, + "dependencies": { + "@gulp-sourcemaps/identity-map": "^2.0.1", + "@gulp-sourcemaps/map-sources": "^1.0.0", + "acorn": "^6.4.1", + "convert-source-map": "^1.0.0", + "css": "^3.0.0", + "debug-fabulous": "^1.0.0", + "detect-newline": "^2.0.0", + "graceful-fs": "^4.0.0", + "source-map": "^0.6.0", + "strip-bom-string": "^1.0.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gulp-sourcemaps/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/gulp-sourcemaps/node_modules/detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-sourcemaps/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-sourcemaps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-sourcemaps/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-sourcemaps/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-terser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gulp-terser/-/gulp-terser-2.0.1.tgz", + "integrity": "sha512-XCrnCXP8ovNpgLK9McJIXlgm0j3W2TsiWu7K9y3m+Sn5XZgUzi6U8MPHtS3NdLMic9poCj695N0ARJ2B6atypw==", + "dev": true, + "dependencies": { + "plugin-error": "^1.0.1", + "terser": "5.4.0", + "through2": "^4.0.2", + "vinyl-sourcemaps-apply": "^0.2.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "deprecated": "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5", + "dev": true, + "dependencies": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/gulp-util/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "node_modules/gulp-util/node_modules/dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/gulp-util/node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "node_modules/gulp-util/node_modules/lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "dependencies": { + "lodash._root": "^3.0.0" + } + }, + "node_modules/gulp-util/node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/gulp-util/node_modules/lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "node_modules/gulp-util/node_modules/lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "node_modules/gulp-util/node_modules/object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-util/node_modules/replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gulp-util/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-util/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/gulp-util/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-util/node_modules/vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "dependencies": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/gulp/node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/gulp/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "node_modules/gulp/node_modules/gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dev": true, + "dependencies": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "node_modules/gulp/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "node_modules/gulp/node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "dev": true + }, + "node_modules/gulp/node_modules/yargs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", + "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.1" + } + }, + "node_modules/gulp/node_modules/yargs-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", + "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + }, + "node_modules/gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "dependencies": { + "glogg": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gzip-size": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", + "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.1", + "pify": "^4.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/gzip-size/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/har-validator/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-base/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/hash-base/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true, + "optional": true + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hast-util-is-element": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz", + "integrity": "sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-sanitize": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-3.0.2.tgz", + "integrity": "sha512-+2I0x2ZCAyiZOO/sb4yNLFmdwPBnyJ4PBkVTUMKMqBwYNA+lXSgOmoRXlJFazoyid9QPogRRKgKhVEodv181sA==", + "dev": true, + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-7.1.3.tgz", + "integrity": "sha512-yk2+1p3EJTEE9ZEUkgHsUSVhIpCsL/bvT8E5GzmWc+N1Po5gBw+0F8bo7dpxXR0nu0bQVxVZGX2lBGF21CmeDw==", + "dev": true, + "dependencies": { + "ccount": "^1.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-is-element": "^1.0.0", + "hast-util-whitespace": "^1.0.0", + "html-void-elements": "^1.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0", + "stringify-entities": "^3.0.1", + "unist-util-is": "^4.0.0", + "xtend": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz", + "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hawk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", + "integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=", + "deprecated": "This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dev": true, + "optional": true, + "dependencies": { + "boom": "0.4.x", + "cryptiles": "0.2.x", + "hoek": "0.9.x", + "sntp": "0.2.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/html-select": { + "version": "2.3.24", + "resolved": "https://registry.npmjs.org/html-select/-/html-select-2.3.24.tgz", + "integrity": "sha1-Rq1tcS5zLPMcZznV0BEKX6vxdYU=", + "dev": true, + "dependencies": { + "cssauron": "^1.1.0", + "duplexer2": "~0.0.2", + "inherits": "^2.0.1", + "minimist": "~0.0.8", + "readable-stream": "^1.0.27-1", + "split": "~0.3.0", + "stream-splicer": "^1.2.0", + "through2": "^1.0.0" + }, + "bin": { + "html-select": "bin/cmd.js" + } + }, + "node_modules/html-select/node_modules/duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "dependencies": { + "readable-stream": "~1.1.9" + } + }, + "node_modules/html-select/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/html-select/node_modules/minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "node_modules/html-select/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/html-select/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/html-select/node_modules/through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "dependencies": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/html-tokenize": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-1.2.5.tgz", + "integrity": "sha1-flupnstR75Buyaf83ubKMmfHiX4=", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "minimist": "~0.0.8", + "readable-stream": "~1.0.27-1", + "through2": "~0.4.1" + }, + "bin": { + "html-tokenize": "bin/cmd.js" + } + }, + "node_modules/html-tokenize/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/html-tokenize/node_modules/minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "node_modules/html-tokenize/node_modules/object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "node_modules/html-tokenize/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/html-tokenize/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/html-tokenize/node_modules/through2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "dev": true, + "dependencies": { + "readable-stream": "~1.0.17", + "xtend": "~2.1.1" + } + }, + "node_modules/html-tokenize/node_modules/xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "dependencies": { + "object-keys": "~0.4.0" + }, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/html-void-elements": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", + "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-browserify": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz", + "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=", + "dev": true, + "dependencies": { + "Base64": "~0.2.0", + "inherits": "~2.0.1" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", + "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true, + "optional": true + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "optional": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignorepatterns": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignorepatterns/-/ignorepatterns-1.0.1.tgz", + "integrity": "sha1-NPOeGlKP34tLS21ZNZypIfYUb6U=", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "node_modules/infinity-agent": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz", + "integrity": "sha1-ReDi/3qesDCyfWK3SzdEt6esQhY=", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/inline-source-map": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.3.1.tgz", + "integrity": "sha1-pSi1FOaJ/OkNswiehw2S9Sestes=", + "dev": true, + "dependencies": { + "source-map": "~0.3.0" + } + }, + "node_modules/inline-source-map/node_modules/source-map": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.3.0.tgz", + "integrity": "sha1-hYb7mloAXltQHiHNGLbyG0V60fk=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/inquirer": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.1.1.tgz", + "integrity": "sha512-hUDjc3vBkh/uk1gPfMAD/7Z188Q8cvTGl0nxwaCdwSbzFh6ZKkZh+s2ozVxbE5G9ZNRyeY0+lgbAIOUFsFf98w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.3.0", + "run-async": "^2.4.0", + "rxjs": "^6.6.6", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/inquirer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/insert-module-globals": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-6.6.3.tgz", + "integrity": "sha1-IGOOKaMPntHKLjqCX7wsulJG3fw=", + "dev": true, + "dependencies": { + "combine-source-map": "~0.6.1", + "concat-stream": "~1.4.1", + "is-buffer": "^1.1.0", + "JSONStream": "^1.0.3", + "lexical-scope": "^1.2.0", + "process": "~0.11.0", + "through2": "^1.0.0", + "xtend": "^4.0.0" + }, + "bin": { + "insert-module-globals": "bin/cmd.js" + } + }, + "node_modules/insert-module-globals/node_modules/combine-source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.6.1.tgz", + "integrity": "sha1-m0oJwxYDPXaODxHgKfonMOB5rZY=", + "dev": true, + "dependencies": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.5.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.4.2" + } + }, + "node_modules/insert-module-globals/node_modules/concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "node_modules/insert-module-globals/node_modules/convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + }, + "node_modules/insert-module-globals/node_modules/inline-source-map": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.5.0.tgz", + "integrity": "sha1-Skxd2OT7Xps82mDIIt+tyu5m4K8=", + "dev": true, + "dependencies": { + "source-map": "~0.4.0" + } + }, + "node_modules/insert-module-globals/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/insert-module-globals/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/insert-module-globals/node_modules/source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/insert-module-globals/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/insert-module-globals/node_modules/through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "dependencies": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arguments": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", + "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", + "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", + "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", + "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-date-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", + "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "dependencies": { + "is-primitive": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "node_modules/is-my-json-valid": { + "version": "2.20.6", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.6.tgz", + "integrity": "sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw==", + "dev": true, + "dependencies": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^5.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", + "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "node_modules/is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", + "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "node_modules/is-running": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", + "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", + "dev": true + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ssh": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.3.tgz", + "integrity": "sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ==", + "dev": true, + "dependencies": { + "protocols": "^1.1.0" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", + "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "dev": true, + "dependencies": { + "text-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", + "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.2", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.0-next.2", + "foreach": "^2.0.5", + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "dev": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "node_modules/istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "deprecated": "This module is no longer maintained, try this instead:\n npm i nyc\nVisit https://istanbul.js.org/integrations for other alternatives.", + "dev": true, + "dependencies": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "istanbul": "lib/cli.js" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/istanbul/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "node_modules/istanbul/node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/istanbul/node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/istanbul/node_modules/js-yaml/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/istanbul/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/istanbul/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "node_modules/istanbul/node_modules/supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/istanbul/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/istextorbinary": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-3.3.0.tgz", + "integrity": "sha512-Tvq1W6NAcZeJ8op+Hq7tdZ434rqnMx4CCZ7H0ff83uEloDvVbqAwaMTZcafKGJT0VHkYzuXUiCY4hlXQg6WfoQ==", + "dev": true, + "dependencies": { + "binaryextensions": "^2.2.0", + "textextensions": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "deprecated": "Jade has been renamed to pug, please install the latest version of pug instead of jade", + "dev": true, + "dependencies": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "bin": { + "jade": "bin/jade" + } + }, + "node_modules/jade/node_modules/commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true, + "engines": { + "node": ">= 0.4.x" + } + }, + "node_modules/jade/node_modules/mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "dev": true, + "dependencies": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest-diff": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.2.tgz", + "integrity": "sha512-BFIdRb0LqfV1hBt8crQmw6gGQHVDhM87SpMIZ45FPYKReZYG5er1+5pIn2zKqvrJp6WNox0ylR8571Iwk2Dmgw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.1", + "jest-get-type": "^27.0.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-get-type": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", + "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.0.2.tgz", + "integrity": "sha512-Qczi5xnTNjkhcIB0Yy75Txt+Ez51xdhOxsukN7awzq2auZQGPHcQrJ623PZj0ECDEMOk2soxWx05EXdXGd1CbA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.0.2", + "jest-get-type": "^27.0.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.0.2.tgz", + "integrity": "sha512-rTqWUX42ec2LdMkoUPOzrEd1Tcm+R1KfLOmFK+OVNo4MnLsEaxO5zPDb2BbdSmthdM/IfXxOZU60P/WbWF8BTw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.0.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-regex-util": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.1.tgz", + "integrity": "sha512-6nY6QVcpTgEKQy1L41P4pr3aOddneK17kn3HJw6SdwGiKfgCGTvH02hVXL0GU8GEKtPH83eD2DIDgxHXOxVohQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "node_modules/jsdoc": { + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.7.tgz", + "integrity": "sha512-sxKt7h0vzCd+3Y81Ey2qinupL6DpRSZJclS04ugHDNmRUXGzqicMJ6iwayhSA0S0DwwX30c5ozyUthr1QKF6uw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.9.4", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.1", + "klaw": "^3.0.0", + "markdown-it": "^10.0.0", + "markdown-it-anchor": "^5.2.7", + "marked": "^2.0.3", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.1" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=8.15.0" + } + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=", + "dev": true, + "dependencies": { + "jju": "^1.1.0" + } + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true, + "dependencies": { + "jsonify": "~0.0.0" + } + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/jsonlint": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.3.tgz", + "integrity": "sha512-jMVTMzP+7gU/IyC6hvKyWpUU8tmTkK5b3BPNuMI9U8Sit+YAWLlZwB6Y6YrdCxfg2kNz05p3XY3Bmm4m26Nv3A==", + "dev": true, + "dependencies": { + "JSV": "^4.0.x", + "nomnom": "^1.5.x" + }, + "bin": { + "jsonlint": "lib/cli.js" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/jsonpointer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", + "integrity": "sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/just-clone": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-1.0.2.tgz", + "integrity": "sha1-v7P672WqEqMWBYcSlFwyb9jwFDQ=" + }, + "node_modules/just-debounce": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", + "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", + "dev": true + }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "node_modules/karma": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.4.tgz", + "integrity": "sha512-hbhRogUYIulfkBTZT7xoPrCYhRBnBoqbbL4fszWD0ReFGUxU+LYBr3dwKdAluaDQ/ynT9/7C+Lf7pPNW4gSx4Q==", + "dev": true, + "dependencies": { + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "colors": "^1.4.0", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.3.0", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^3.1.0", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.28", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-babel-preprocessor": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/karma-babel-preprocessor/-/karma-babel-preprocessor-8.0.1.tgz", + "integrity": "sha512-5upyawNi3c7Gg6tPH1FWRVTmUijGf3v1GV4ScLM/2jKdDP18SlaKlUpu8eJrRI3STO8qK1bkqFcdgAA364nLYQ==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "@babel/core": "7" + } + }, + "node_modules/karma-browserstack-launcher": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.4.0.tgz", + "integrity": "sha512-bUQK84U+euDfOUfEjcF4IareySMOBNRLrrl9q6cttIe8f011Ir6olLITTYMOJDcGY58wiFIdhPHSPd9Pi6+NfQ==", + "dev": true, + "dependencies": { + "browserstack": "~1.5.1", + "browserstacktunnel-wrapper": "~2.0.2", + "q": "~1.5.0" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-chai": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", + "integrity": "sha1-vuWtQEAFF4Ea40u5RfdikJEIt5o=", + "dev": true, + "peerDependencies": { + "chai": "*", + "karma": ">=0.10.9" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", + "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "dev": true, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.0.3.tgz", + "integrity": "sha512-atDvLQqvPcLxhED0cmXYdsPMCQuh6Asa9FMZW1bhNqlVEhJoB9qyZ2BY1gu7D/rr5GLGb5QzYO4siQskxaWP/g==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.1", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.0", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/karma-coverage-istanbul-reporter": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-3.0.3.tgz", + "integrity": "sha512-wE4VFhG/QZv2Y4CdAYWDbMmcAHeS926ZIji4z+FkB2aF/EposRb6DP6G5ncT/wXhqUfAb/d7kZrNKPonbvsATw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^3.0.2", + "minimatch": "^3.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/mattlewis92" + } + }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/istanbul-lib-source-maps/node_modules/istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma-es5-shim": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/karma-es5-shim/-/karma-es5-shim-0.0.4.tgz", + "integrity": "sha1-zdADM8znfC5M4D46yT8vjs0fuVI=", + "dev": true, + "dependencies": { + "es5-shim": "^4.0.5" + } + }, + "node_modules/karma-firefox-launcher": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.1.tgz", + "integrity": "sha512-VzDMgPseXak9DtfyE1O5bB2BwsMy1zzO1kUxVW1rP0yhC4tDNJ0p3JoFdzvrK4QqVzdqUMa9Rx9YzkdFp8hz3Q==", + "dev": true, + "dependencies": { + "is-wsl": "^2.2.0", + "which": "^2.0.1" + } + }, + "node_modules/karma-ie-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-ie-launcher/-/karma-ie-launcher-1.0.0.tgz", + "integrity": "sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw=", + "dev": true, + "dependencies": { + "lodash": "^4.6.1" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-mocha": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", + "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3" + } + }, + "node_modules/karma-mocha-reporter": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz", + "integrity": "sha1-FRIAlejtgZGG5HoLAS8810GJVWA=", + "dev": true, + "dependencies": { + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "strip-ansi": "^4.0.0" + }, + "peerDependencies": { + "karma": ">=0.13" + } + }, + "node_modules/karma-mocha-reporter/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/karma-mocha-reporter/node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/karma-mocha-reporter/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/karma-opera-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-opera-launcher/-/karma-opera-launcher-1.0.0.tgz", + "integrity": "sha1-+lFihTGh0L6EstjcDX7iCfyP+Ro=", + "dev": true, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-safari-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz", + "integrity": "sha1-lpgqLMR9BmquccVTursoMZEVos4=", + "dev": true, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-script-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-script-launcher/-/karma-script-launcher-1.0.0.tgz", + "integrity": "sha1-zQF8TeXvCeWp2nkydhdhCN1LVC0=", + "dev": true, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-sinon": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/karma-sinon/-/karma-sinon-1.0.5.tgz", + "integrity": "sha1-TjRD8oMP3s/2JNN0cWPxIX2qKpo=", + "dev": true, + "engines": { + "node": ">= 0.10.0" + }, + "peerDependencies": { + "karma": ">=0.10", + "sinon": "*" + } + }, + "node_modules/karma-sourcemap-loader": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.8.tgz", + "integrity": "sha512-zorxyAakYZuBcHRJE+vbrK2o2JXLFWK8VVjiT/6P+ltLBUGUvqTEkUiQ119MGdOrK7mrmxXHZF1/pfT6GgIZ6g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2" + } + }, + "node_modules/karma-spec-reporter": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.32.tgz", + "integrity": "sha1-LpxyB+pyZ3EmAln4K+y1QyCeRAo=", + "dev": true, + "dependencies": { + "colors": "^1.1.2" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-webpack": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-3.0.5.tgz", + "integrity": "sha512-nRudGJWstvVuA6Tbju9tyGUfXTtI1UXMXoRHVmM2/78D0q6s/Ye2IC157PKNDC15PWFGR0mVIRtWLAdcfsRJoA==", + "dev": true, + "dependencies": { + "async": "^2.0.0", + "babel-runtime": "^6.0.0", + "loader-utils": "^1.0.0", + "lodash": "^4.0.0", + "source-map": "^0.5.6", + "webpack-dev-middleware": "^2.0.6" + }, + "engines": { + "node": ">= 4" + }, + "peerDependencies": { + "webpack": "^2.0.0 || ^3.0.0" + } + }, + "node_modules/karma-webpack/node_modules/async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/karma-webpack/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/karma-webpack/node_modules/loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/karma/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/karma/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/karma/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/kew": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.1.7.tgz", + "integrity": "sha1-CjKoF/8amzsSuMm6z0vE1nmvjnI=", + "dev": true + }, + "node_modules/keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/konan": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", + "integrity": "sha512-7ZhYV84UzJ0PR/RJnnsMZcAbn+kLasJhVNWsu8ZyVEJYRpGA5XESQ9d/7zOa08U0Ou4cmB++hMNY/3OSV9KIbg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.10.5", + "@babel/traverse": "^7.10.5" + } + }, + "node_modules/labeled-stream-splicer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-1.0.2.tgz", + "integrity": "sha1-RhUzFTd4SYHo/SZOHzpDTE4N3WU=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "isarray": "~0.0.1", + "stream-splicer": "^1.1.0" + } + }, + "node_modules/labeled-stream-splicer/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "dependencies": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "dependencies": { + "invert-kv": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "dev": true, + "bin": { + "lcov-parse": "bin/cli.js" + } + }, + "node_modules/lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "dependencies": { + "flush-write-stream": "^1.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lexical-scope": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", + "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=", + "dev": true, + "dependencies": { + "astw": "^2.0.0" + } + }, + "node_modules/liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "dependencies": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.2.0.tgz", + "integrity": "sha512-wzUvdIeJZhRsG6gpZfmSCfysaxNEr43i+QT+Hie94wvHDKFLi4n7C2GqZ4sTC+PH5b5iktmXJvU87rWvhP3lHw==", + "dev": true, + "dependencies": { + "debug": "^2.6.8", + "marky": "^1.2.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "node_modules/linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=", + "dev": true + }, + "node_modules/live-connect-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.0.0.tgz", + "integrity": "sha512-Xhrj1JU5LoLjJuujjTlvDfc/n3Shzk2hPlYmLdCx/lsltFFVuCFa9uM8u5mcHlmOUKP5pu9I54bAITxZBMHoXg==", + "dependencies": { + "tiny-hashes": "1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/livereload-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", + "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash._arraycopy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", + "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=", + "dev": true + }, + "node_modules/lodash._arrayeach": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", + "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=", + "dev": true + }, + "node_modules/lodash._arraymap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraymap/-/lodash._arraymap-3.0.0.tgz", + "integrity": "sha1-Go/Q9MDfS2HeoHbXF83Jfwo8PmY=", + "dev": true + }, + "node_modules/lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash._baseassign/node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash._baseclone": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", + "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", + "dev": true, + "dependencies": { + "lodash._arraycopy": "^3.0.0", + "lodash._arrayeach": "^3.0.0", + "lodash._baseassign": "^3.0.0", + "lodash._basefor": "^3.0.0", + "lodash.isarray": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash._baseclone/node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "node_modules/lodash._basedifference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basedifference/-/lodash._basedifference-3.0.3.tgz", + "integrity": "sha1-8sIEKWwqeOArOJCBtu3KyTPPYpw=", + "dev": true, + "dependencies": { + "lodash._baseindexof": "^3.0.0", + "lodash._cacheindexof": "^3.0.0", + "lodash._createcache": "^3.0.0" + } + }, + "node_modules/lodash._baseflatten": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz", + "integrity": "sha1-B3D/gBMa9uNPO1EXlqe6UhTmX/c=", + "dev": true, + "dependencies": { + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash._basefor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", + "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=", + "dev": true + }, + "node_modules/lodash._baseindexof": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", + "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", + "dev": true + }, + "node_modules/lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "node_modules/lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "node_modules/lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "node_modules/lodash._cacheindexof": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", + "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", + "dev": true + }, + "node_modules/lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "dev": true, + "dependencies": { + "lodash._bindcallback": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "node_modules/lodash._createcache": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", + "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", + "dev": true, + "dependencies": { + "lodash._getnative": "^3.0.0" + } + }, + "node_modules/lodash._escapehtmlchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", + "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", + "dev": true, + "dependencies": { + "lodash._htmlescapes": "~2.4.1" + } + }, + "node_modules/lodash._escapestringchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", + "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=", + "dev": true + }, + "node_modules/lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "node_modules/lodash._htmlescapes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", + "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=", + "dev": true + }, + "node_modules/lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "node_modules/lodash._isnative": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", + "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", + "dev": true + }, + "node_modules/lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", + "dev": true + }, + "node_modules/lodash._pickbyarray": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash._pickbyarray/-/lodash._pickbyarray-3.0.2.tgz", + "integrity": "sha1-H4mNlgfrVgsOFnOEt3x8bRCKpMU=", + "dev": true + }, + "node_modules/lodash._pickbycallback": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._pickbycallback/-/lodash._pickbycallback-3.0.0.tgz", + "integrity": "sha1-/2G5oBens699MObFPeKK+hm4dQo=", + "dev": true, + "dependencies": { + "lodash._basefor": "^3.0.0", + "lodash.keysin": "^3.0.0" + } + }, + "node_modules/lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "node_modules/lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "node_modules/lodash._reinterpolate": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", + "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=", + "dev": true + }, + "node_modules/lodash._reunescapedhtml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", + "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", + "dev": true, + "dependencies": { + "lodash._htmlescapes": "~2.4.1", + "lodash.keys": "~2.4.1" + } + }, + "node_modules/lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, + "node_modules/lodash._shimkeys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", + "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", + "dev": true, + "dependencies": { + "lodash._objecttypes": "~2.4.1" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true, + "optional": true + }, + "node_modules/lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", + "dev": true + }, + "node_modules/lodash.escape": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", + "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", + "dev": true, + "dependencies": { + "lodash._escapehtmlchar": "~2.4.1", + "lodash._reunescapedhtml": "~2.4.1", + "lodash.keys": "~2.4.1" + } + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "node_modules/lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "node_modules/lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", + "dev": true + }, + "node_modules/lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "node_modules/lodash.istypedarray": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", + "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=", + "dev": true + }, + "node_modules/lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "dependencies": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "node_modules/lodash.keys/node_modules/lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dev": true, + "dependencies": { + "lodash._objecttypes": "~2.4.1" + } + }, + "node_modules/lodash.keysin": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz", + "integrity": "sha1-IsRJPrvtsUJ5YqVLRFssinZ/tH8=", + "dev": true, + "dependencies": { + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.omit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-3.1.0.tgz", + "integrity": "sha1-iX/jguZBPZrJfGH3jtHgV6AK+fM=", + "dev": true, + "dependencies": { + "lodash._arraymap": "^3.0.0", + "lodash._basedifference": "^3.0.0", + "lodash._baseflatten": "^3.0.0", + "lodash._bindcallback": "^3.0.0", + "lodash._pickbyarray": "^3.0.0", + "lodash._pickbycallback": "^3.0.0", + "lodash.keysin": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "node_modules/lodash.pickby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", + "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=", + "dev": true + }, + "node_modules/lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "node_modules/lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", + "dev": true + }, + "node_modules/lodash.template": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", + "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", + "dev": true, + "dependencies": { + "lodash._escapestringchar": "~2.4.1", + "lodash._reinterpolate": "~2.4.1", + "lodash.defaults": "~2.4.1", + "lodash.escape": "~2.4.1", + "lodash.keys": "~2.4.1", + "lodash.templatesettings": "~2.4.1", + "lodash.values": "~2.4.1" + } + }, + "node_modules/lodash.template/node_modules/lodash.defaults": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", + "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", + "dev": true, + "dependencies": { + "lodash._objecttypes": "~2.4.1", + "lodash.keys": "~2.4.1" + } + }, + "node_modules/lodash.templatesettings": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", + "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "~2.4.1", + "lodash.escape": "~2.4.1" + } + }, + "node_modules/lodash.toplainobject": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz", + "integrity": "sha1-KHkK2ULSk9eKpmOgfs9/UsoEGY0=", + "dev": true, + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash.keysin": "^3.0.0" + } + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", + "dev": true + }, + "node_modules/lodash.values": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", + "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", + "dev": true, + "dependencies": { + "lodash.keys": "~2.4.1" + } + }, + "node_modules/lodash.zip": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", + "integrity": "sha1-7GZi5IlkCO1KtsVCo5kLcswIACA=", + "dev": true + }, + "node_modules/log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true, + "engines": { + "node": ">=0.8.6" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log4js": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", + "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", + "dev": true, + "dependencies": { + "date-format": "^3.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.1", + "rfdc": "^1.1.4", + "streamroller": "^2.2.4" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/log4js/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/log4js/node_modules/flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "node_modules/log4js/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/loglevel": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", + "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", + "dev": true + }, + "node_modules/loglevelnext": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/loglevelnext/-/loglevelnext-1.0.5.tgz", + "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==", + "dev": true, + "dependencies": { + "es6-symbol": "^3.1.1", + "object.assign": "^4.1.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + }, + "node_modules/longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/longest-streak": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "dependencies": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dev": true, + "dependencies": { + "es5-ext": "~0.10.2" + } + }, + "node_modules/magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "optional": true, + "dependencies": { + "sourcemap-codec": "^1.4.4" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", + "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", + "dev": true, + "peerDependencies": { + "markdown-it": "*" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/markdownlint": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.0.6.tgz", + "integrity": "sha1-8OPav/D4ZeGMrRy0f+80vLY4qG0=", + "dev": true, + "dependencies": { + "markdown-it": "^4.2.2" + } + }, + "node_modules/markdownlint/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/markdownlint/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "node_modules/markdownlint/node_modules/linkify-it": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-1.2.4.tgz", + "integrity": "sha1-B3NSbDF8j9E71TTuHRgP+Iq/iBo=", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/markdownlint/node_modules/markdown-it": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-4.4.0.tgz", + "integrity": "sha1-PfNz2+pYepp/7z5WMRtokI91xBQ=", + "dev": true, + "dependencies": { + "argparse": "~1.0.2", + "entities": "~1.1.1", + "linkify-it": "~1.2.0", + "mdurl": "~1.0.0", + "uc.micro": "^1.0.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/marked": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", + "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", + "dev": true, + "bin": { + "marked": "bin/marked" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/marky": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.2.tgz", + "integrity": "sha512-k1dB2HNeaNyORco8ulVEhctyEGkKHb2YWAhDsxeFlW2nROIirsctBYzKwwS3Vza+sKTS1zO4Z+n9/+9WbGLIxQ==", + "dev": true + }, + "node_modules/matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, + "dependencies": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/matchdep/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/matchdep/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "dev": true + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdast-util-definitions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", + "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", + "dev": true, + "dependencies": { + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz", + "integrity": "sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^4.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-0.1.2.tgz", + "integrity": "sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==", + "dev": true, + "dependencies": { + "mdast-util-gfm-autolink-literal": "^0.1.0", + "mdast-util-gfm-strikethrough": "^0.2.0", + "mdast-util-gfm-table": "^0.1.0", + "mdast-util-gfm-task-list-item": "^0.1.0", + "mdast-util-to-markdown": "^0.6.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.3.tgz", + "integrity": "sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==", + "dev": true, + "dependencies": { + "ccount": "^1.0.0", + "mdast-util-find-and-replace": "^1.1.0", + "micromark": "^2.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz", + "integrity": "sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==", + "dev": true, + "dependencies": { + "mdast-util-to-markdown": "^0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.6.tgz", + "integrity": "sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==", + "dev": true, + "dependencies": { + "markdown-table": "^2.0.0", + "mdast-util-to-markdown": "~0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz", + "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==", + "dev": true, + "dependencies": { + "mdast-util-to-markdown": "~0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-inject": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz", + "integrity": "sha1-2wa4tYW+lZotzS+H9HK6m3VvNnU=", + "dev": true, + "dependencies": { + "mdast-util-to-string": "^1.0.0" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz", + "integrity": "sha512-JoPBfJ3gBnHZ18icCwHR50orC9kNH81tiR1gs01D8Q5YpV6adHNO9nKNuFBCJQ941/32PT1a63UF/DitmS3amQ==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "mdast-util-definitions": "^4.0.0", + "mdurl": "^1.0.0", + "unist-builder": "^2.0.0", + "unist-util-generated": "^1.0.0", + "unist-util-position": "^3.0.0", + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", + "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "longest-streak": "^2.0.0", + "mdast-util-to-string": "^2.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.0.0", + "zwitch": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", + "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-toc": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-5.1.0.tgz", + "integrity": "sha512-csimbRIVkiqc+PpFeKDGQ/Ck2N4f9FYH3zzBMMJzcxoKL8m+cM0n94xXm0I9eaxHnKdY9n145SGTdyJC7i273g==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.3", + "@types/unist": "^2.0.3", + "extend": "^3.0.2", + "github-slugger": "^1.2.1", + "mdast-util-to-string": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-toc/node_modules/emoji-regex": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", + "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=", + "dev": true + }, + "node_modules/mdast-util-toc/node_modules/github-slugger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz", + "integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==", + "dev": true, + "dependencies": { + "emoji-regex": ">=6.0.0 <=6.1.1" + } + }, + "node_modules/mdast-util-toc/node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mem/node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/memoizee": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + } + }, + "node_modules/memoizee/node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + }, + "node_modules/memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "node_modules/memory-fs/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/memory-fs/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/meow/node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/hosted-git-info": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/map-obj": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", + "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/normalize-package-data": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", + "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/meow/node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/meow/node_modules/read-pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/meow/node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/merge-source-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz", + "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==", + "dev": true, + "dependencies": { + "micromark": "~2.11.0", + "micromark-extension-gfm-autolink-literal": "~0.5.0", + "micromark-extension-gfm-strikethrough": "~0.6.5", + "micromark-extension-gfm-table": "~0.4.0", + "micromark-extension-gfm-tagfilter": "~0.3.0", + "micromark-extension-gfm-task-list-item": "~0.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz", + "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==", + "dev": true, + "dependencies": { + "micromark": "~2.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz", + "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==", + "dev": true, + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz", + "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==", + "dev": true, + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz", + "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz", + "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==", + "dev": true, + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/micromark/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "dependencies": { + "mime-db": "1.48.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "dependencies": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/mocha-phantomjs": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/mocha-phantomjs/-/mocha-phantomjs-3.6.0.tgz", + "integrity": "sha1-mxN/yMiRuPpd1ln3OOyVRYt0+5c=", + "dev": true, + "dependencies": { + "commander": "~2.0.0", + "mocha": "~1.20.1" + }, + "bin": { + "mocha-phantomjs": "bin/mocha-phantomjs" + }, + "peerDependencies": { + "phantomjs": "1.9.1 - 1.9.7-15" + } + }, + "node_modules/mocha-phantomjs/node_modules/commander": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=", + "dev": true, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/mocha-phantomjs/node_modules/diff": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha-phantomjs/node_modules/glob": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "dev": true, + "dependencies": { + "graceful-fs": "~2.0.0", + "inherits": "2", + "minimatch": "~0.2.11" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha-phantomjs/node_modules/graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", + "deprecated": "please upgrade to graceful-fs 4 for compatibility with current and future versions of Node.js", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/mocha-phantomjs/node_modules/growl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=", + "dev": true + }, + "node_modules/mocha-phantomjs/node_modules/lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "node_modules/mocha-phantomjs/node_modules/minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "dependencies": { + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha-phantomjs/node_modules/mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true + }, + "node_modules/mocha-phantomjs/node_modules/mocha": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.20.1.tgz", + "integrity": "sha1-80ODLZ/gx9l8ZPxwRI9RNt+f7Vs=", + "deprecated": "Mocha v1.x is no longer supported.", + "dev": true, + "dependencies": { + "commander": "2.0.0", + "debug": "*", + "diff": "1.0.7", + "glob": "3.2.3", + "growl": "1.7.x", + "jade": "0.26.3", + "mkdirp": "0.3.5" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 0.4.x" + } + }, + "node_modules/mocha/node_modules/commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "node_modules/mocha/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/mocha/node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "node_modules/mocha/node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/module-deps": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-3.9.1.tgz", + "integrity": "sha1-6nXK+RmQkNJbDVUStaysuW5/h/M=", + "dev": true, + "dependencies": { + "browser-resolve": "^1.7.0", + "concat-stream": "~1.4.5", + "defined": "^1.0.0", + "detective": "^4.0.0", + "duplexer2": "0.0.2", + "inherits": "^2.0.1", + "JSONStream": "^1.0.3", + "parents": "^1.0.0", + "readable-stream": "^1.1.13", + "resolve": "^1.1.3", + "stream-combiner2": "~1.0.0", + "subarg": "^1.0.0", + "through2": "^1.0.0", + "xtend": "^4.0.0" + }, + "bin": { + "module-deps": "bin/cmd.js" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/module-deps-sortable": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/module-deps-sortable/-/module-deps-sortable-5.0.3.tgz", + "integrity": "sha512-eiyIZj/A0dj1o4ywXWqicazUL3l0HP3TydUR6xF0X3xh3LGBMLqW8a9aFe6MuNH4mxNMk53QKBHM6LOPR8kSgw==", + "dev": true, + "dependencies": { + "browser-resolve": "^1.7.0", + "cached-path-relative": "^1.0.0", + "concat-stream": "~1.5.0", + "defined": "^1.0.0", + "detective": "^5.2.0", + "duplexer2": "^0.1.2", + "inherits": "^2.0.1", + "JSONStream": "^1.0.3", + "konan": "^2.1.1", + "readable-stream": "^2.0.2", + "resolve": "^1.1.3", + "standard-version": "^9.0.0", + "stream-combiner2": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "bin": { + "module-deps": "bin/cmd.js" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/module-deps-sortable/node_modules/concat-stream": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "~2.0.0", + "typedarray": "~0.0.5" + } + }, + "node_modules/module-deps-sortable/node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/module-deps-sortable/node_modules/process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "node_modules/module-deps-sortable/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/module-deps-sortable/node_modules/readable-stream/node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/module-deps-sortable/node_modules/readable-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/module-deps-sortable/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/module-deps-sortable/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/module-deps/node_modules/acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/module-deps/node_modules/concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "node_modules/module-deps/node_modules/detective": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", + "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", + "dev": true, + "dependencies": { + "acorn": "^5.2.1", + "defined": "^1.0.0" + } + }, + "node_modules/module-deps/node_modules/duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "dependencies": { + "readable-stream": "~1.1.9" + } + }, + "node_modules/module-deps/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/module-deps/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/module-deps/node_modules/stream-combiner2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.0.2.tgz", + "integrity": "sha1-unKmtQy/q/qVD8i8h2BL0B62BnE=", + "dev": true, + "dependencies": { + "duplexer2": "~0.0.2", + "through2": "~0.5.1" + } + }, + "node_modules/module-deps/node_modules/stream-combiner2/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/module-deps/node_modules/stream-combiner2/node_modules/through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "dependencies": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + }, + "node_modules/module-deps/node_modules/stream-combiner2/node_modules/xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/module-deps/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/module-deps/node_modules/through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "dependencies": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", + "dev": true + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "dependencies": { + "duplexer2": "0.0.2" + } + }, + "node_modules/multipipe/node_modules/duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "dependencies": { + "readable-stream": "~1.1.9" + } + }, + "node_modules/multipipe/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/multipipe/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/multipipe/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "dev": true, + "optional": true + }, + "node_modules/nanoid": { + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/natives": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", + "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==", + "deprecated": "This module relies on Node.js's internals and will break at some point. Do not use it, and update to graceful-fs@4.x.", + "dev": true, + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/ncp": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz", + "integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ=", + "dev": true, + "bin": { + "ncp": "bin/ncp" + } + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nested-error-stacks": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz", + "integrity": "sha1-GfYZWRUZ8JZ2mlupqG5u7sgjw88=", + "dev": true, + "dependencies": { + "inherits": "~2.0.1" + } + }, + "node_modules/next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/nise": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", + "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "dev": true, + "dependencies": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^5.0.1", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/formatio": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "node_modules/nise/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/nise/node_modules/lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "dependencies": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + } + }, + "node_modules/node-libs-browser/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/node-libs-browser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "node_modules/node-libs-browser/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/node-libs-browser/node_modules/readable-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/node-releases": { + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", + "dev": true + }, + "node_modules/node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", + "deprecated": "Use uuid module instead", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/nomnom": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", + "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=", + "deprecated": "Package no longer supported. Contact support@npmjs.com for more info.", + "dev": true, + "dependencies": { + "chalk": "~0.4.0", + "underscore": "~1.6.0" + } + }, + "node_modules/nomnom/node_modules/ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nomnom/node_modules/chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true, + "dependencies": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nomnom/node_modules/strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true, + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nomnom/node_modules/underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/nopt-usage": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/nopt-usage/-/nopt-usage-0.1.0.tgz", + "integrity": "sha1-sYuMGD4YEEfKnmO3zefPxwLMpXk=", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "dependencies": { + "once": "^1.3.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/npm-license": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/npm-license/-/npm-license-0.2.3.tgz", + "integrity": "sha1-49ARePz4zcyWg53E06Z9ZxE/vYs=", + "dev": true, + "dependencies": { + "mkdirp": "~0.5.0", + "nopt": "~3.0.1", + "nopt-usage": "^0.1.0", + "package-license": "~0.1.1", + "pkginfo": "^0.3.0", + "read-installed": "~3.1.3", + "treeify": "~1.0.1", + "underscore": "~1.4.4" + }, + "bin": { + "npm-license": "bin/npm-license" + } + }, + "node_modules/npm-license/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/npm-license/node_modules/underscore": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", + "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=", + "dev": true + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npmconf": { + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/npmconf/-/npmconf-0.0.24.tgz", + "integrity": "sha1-t4h1sIjMw8Cvo+zrPOMkSxtSOQw=", + "deprecated": "this package has been reintegrated into npm and is now out of date with respect to npm", + "dev": true, + "dependencies": { + "config-chain": "~1.1.1", + "inherits": "~1.0.0", + "ini": "~1.1.0", + "mkdirp": "~0.3.3", + "nopt": "2", + "once": "~1.1.1", + "osenv": "0.0.3", + "semver": "~1.1.0" + } + }, + "node_modules/npmconf/node_modules/inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "node_modules/npmconf/node_modules/ini": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.1.0.tgz", + "integrity": "sha1-ToCMLOFExsF4iRjgNNZ5e8bPYoE=", + "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/npmconf/node_modules/mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true + }, + "node_modules/npmconf/node_modules/nopt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.2.1.tgz", + "integrity": "sha1-KqCbfRdoSHs7ianFqlIzW/8Lrqc=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/npmconf/node_modules/once": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/once/-/once-1.1.1.tgz", + "integrity": "sha1-nbV0kzzLCMOnYU0VQDLAnqbzOec=", + "dev": true + }, + "node_modules/npmconf/node_modules/semver": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-1.1.4.tgz", + "integrity": "sha1-LlpOcrqwNHLMl/cnU7RQiRLvVUA=", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/null-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", + "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "dependencies": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.omit/node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.values": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", + "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/open/-/open-0.0.3.tgz", + "integrity": "sha1-+jd/T/MIIS2SqbjmOVJAhUZGpxM=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/opn/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "dependencies": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "node_modules/optimist/node_modules/minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "node_modules/optimist/node_modules/wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/ordered-read-streams/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/ordered-read-streams/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "node_modules/os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "dependencies": { + "lcid": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.0.3.tgz", + "integrity": "sha1-zWrY3bKQkVrZ4idlV2Al1BHynLY=", + "dev": true + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-license": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/package-license/-/package-license-0.1.2.tgz", + "integrity": "sha1-9cRJp6UPrfSiI0HilJCZ+RpCAos=", + "dev": true + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "dependencies": { + "path-platform": "~0.11.15" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-github-repo-url": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", + "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=", + "dev": true + }, + "node_modules/parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "dependencies": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-glob/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-glob/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-path": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.3.tgz", + "integrity": "sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA==", + "dev": true, + "dependencies": { + "is-ssh": "^1.3.0", + "protocols": "^1.4.0", + "qs": "^6.9.4", + "query-string": "^6.13.8" + } + }, + "node_modules/parse-path/node_modules/qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/parse-url": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.5.tgz", + "integrity": "sha512-AwfVhXaQrNNI6UPUJq/GJN2qoY0L9gPgxhh9VbDP0bfBAJWaC/Zh8hjQ58YKTi4AagOT70fpadkYSKPo+eFb1w==", + "dev": true, + "dependencies": { + "is-ssh": "^1.3.0", + "normalize-url": "4.5.0", + "parse-path": "^4.0.0", + "protocols": "^1.4.0" + } + }, + "node_modules/parse-url/node_modules/normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/pbkdf2-compat": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz", + "integrity": "sha1-tuDI+plJTZTgURV1gCpZpcFC8og=", + "dev": true + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "node_modules/phantomjs": { + "version": "1.9.7-15", + "resolved": "https://registry.npmjs.org/phantomjs/-/phantomjs-1.9.7-15.tgz", + "integrity": "sha1-Czp85jBIaoO+kf9Ogy7uIOlxEVs=", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "adm-zip": "0.2.1", + "kew": "~0.1.7", + "mkdirp": "0.3.5", + "ncp": "0.4.2", + "npmconf": "0.0.24", + "progress": "^1.1.5", + "request": "2.36.0", + "request-progress": "^0.3.1", + "rimraf": "~2.2.2", + "which": "~1.0.5" + }, + "bin": { + "phantomjs": "bin/phantomjs" + } + }, + "node_modules/phantomjs/node_modules/asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.9" + } + }, + "node_modules/phantomjs/node_modules/assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/phantomjs/node_modules/aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/phantomjs/node_modules/combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "optional": true, + "dependencies": { + "delayed-stream": "0.0.5" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/phantomjs/node_modules/delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/phantomjs/node_modules/forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/phantomjs/node_modules/form-data": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", + "dev": true, + "optional": true, + "dependencies": { + "async": "~0.9.0", + "combined-stream": "~0.0.4", + "mime": "~1.2.11" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/phantomjs/node_modules/hawk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.0.0.tgz", + "integrity": "sha1-uQuxaYByhUEdp//LjdJZhQLTtS0=", + "deprecated": "This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dev": true, + "optional": true, + "dependencies": { + "boom": "0.4.x", + "cryptiles": "0.2.x", + "hoek": "0.9.x", + "sntp": "0.2.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/phantomjs/node_modules/http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "dev": true, + "optional": true, + "dependencies": { + "asn1": "0.1.11", + "assert-plus": "^0.1.5", + "ctype": "0.5.3" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/phantomjs/node_modules/mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true + }, + "node_modules/phantomjs/node_modules/mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true + }, + "node_modules/phantomjs/node_modules/oauth-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", + "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/phantomjs/node_modules/progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/phantomjs/node_modules/qs": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz", + "integrity": "sha1-bgFQmP9RlouKPIGQAdXyyJvEsQc=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/phantomjs/node_modules/request": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.36.0.tgz", + "integrity": "sha1-KMbAQmLHuf/dIbklU3RRfubZQ/U=", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "dependencies": { + "forever-agent": "~0.5.0", + "json-stringify-safe": "~5.0.0", + "mime": "~1.2.9", + "node-uuid": "~1.4.0", + "qs": "~0.6.0" + }, + "optionalDependencies": { + "aws-sign2": "~0.5.0", + "form-data": "~0.1.0", + "hawk": "~1.0.0", + "http-signature": "~0.10.0", + "oauth-sign": "~0.3.0", + "tough-cookie": ">=0.12.0", + "tunnel-agent": "~0.4.0" + } + }, + "node_modules/phantomjs/node_modules/rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/phantomjs/node_modules/tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/phantomjs/node_modules/which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true, + "bin": { + "which": "bin/which" + } + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "dev": true, + "dependencies": { + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-up/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkginfo": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", + "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/plugin-error/node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/portfinder": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-0.2.1.tgz", + "integrity": "sha1-srmwFk+eF/o6nH2yME0KdRQMca0=", + "dev": true, + "dependencies": { + "mkdirp": "0.0.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/portfinder/node_modules/mkdirp": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.7.tgz", + "integrity": "sha1-2JtPDkw+XlylQjWTFnXglP4aUHI=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz", + "integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==", + "dev": true, + "optional": true, + "dependencies": { + "colorette": "^1.2.2", + "nanoid": "^3.1.23", + "source-map-js": "^0.6.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-modules": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.1.3.tgz", + "integrity": "sha512-dBT39hrXe4OAVYJe/2ZuIZ9BzYhOe7t+IhedYeQ2OxKwDpAGlkEN/fR0fGnrbx4BvgbMReRX4hCubYK9cE/pJQ==", + "dev": true, + "optional": true, + "dependencies": { + "generic-names": "^2.0.1", + "icss-replace-symbols": "^1.1.0", + "lodash.camelcase": "^4.3.0", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "string-hash": "^1.1.1" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "optional": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "optional": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "optional": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "optional": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", + "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", + "dev": true, + "optional": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true, + "optional": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pretty-format": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.2.tgz", + "integrity": "sha512-mXKbbBPnYTG7Yra9qFBtqj+IXcsvxsvOBco3QHxtxTl+hHKq6QdzMZ+q0CtL4ORHZgwGImRr2XZUX2EWzORxig==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "dev": true, + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dev": true, + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true + }, + "node_modules/protocols": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz", + "integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==", + "dev": true + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/proxyquire": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-1.8.0.tgz", + "integrity": "sha1-AtUUpb7ZhvBMuyCTrxZ0FTX3ntw=", + "dev": true, + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.0", + "resolve": "~1.1.7" + } + }, + "node_modules/proxyquire/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "node_modules/ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "dependencies": { + "event-stream": "=3.3.4" + }, + "bin": { + "ps-tree": "bin/ps-tree.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer-core": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-9.1.1.tgz", + "integrity": "sha512-zbedbitVIGhmgz0nt7eIdLsnaoVZSlNJfBivqm2w67T8LR2bU1dvnruDZ8nQO0zn++Iet7zHbAOdnuS5+H2E7A==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.869402", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer-core/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.869402", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", + "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "dev": true + }, + "node_modules/puppeteer-core/node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/puppeteer-core/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/query-selector-shadow-dom": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.0.tgz", + "integrity": "sha512-bK0/0cCI+R8ZmOF1QjT7HupDUYCxbf/9TJgAmSXQxZpftXmTAeil9DRoCnTDkWbvOyZzhcMBwKpptWcdkGFIMg==", + "dev": true + }, + "node_modules/query-string": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", + "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", + "dev": true, + "dependencies": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dev": true, + "dependencies": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/randomatic/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/read-all-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", + "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-all-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/read-all-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/read-installed": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-3.1.5.tgz", + "integrity": "sha1-SuNgga/T4iBNwuJ5gHqqUsMMjAw=", + "dev": true, + "dependencies": { + "debuglog": "^1.0.1", + "read-package-json": "1", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + }, + "optionalDependencies": { + "graceful-fs": "2 || 3" + } + }, + "node_modules/read-installed/node_modules/graceful-fs": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", + "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", + "dev": true, + "optional": true, + "dependencies": { + "natives": "^1.1.3" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/read-installed/node_modules/semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-package-json": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-1.3.3.tgz", + "integrity": "sha1-73nf2kbhZTdu6KV++/7dTRsCm6Q=", + "dev": true, + "dependencies": { + "glob": "^5.0.3", + "json-parse-helpfulerror": "^1.0.2", + "normalize-package-data": "^1.0.0" + }, + "optionalDependencies": { + "graceful-fs": "2 || 3" + } + }, + "node_modules/read-package-json/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/read-package-json/node_modules/graceful-fs": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", + "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", + "dev": true, + "optional": true, + "dependencies": { + "natives": "^1.1.3" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/read-package-json/node_modules/normalize-package-data": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-1.0.3.tgz", + "integrity": "sha1-i+lVuJB6+XXxpFhOqLubQUkjEvU=", + "dev": true, + "dependencies": { + "github-url-from-git": "^1.3.0", + "github-url-from-username-repo": "^1.0.0", + "semver": "2 || 3 || 4" + } + }, + "node_modules/read-package-json/node_modules/semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readable-wrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readable-wrap/-/readable-wrap-1.0.0.tgz", + "integrity": "sha1-O1ohHGMeEjA6VJkcgGwX564ga/8=", + "dev": true, + "dependencies": { + "readable-stream": "^1.1.13-1" + } + }, + "node_modules/readable-wrap/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/readable-wrap/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/readable-wrap/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "dev": true, + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readline2": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz", + "integrity": "sha1-mUQ7pug7gw7zBRv9fcJBqCco1Wg=", + "dev": true, + "dependencies": { + "mute-stream": "0.0.4", + "strip-ansi": "^2.0.1" + } + }, + "node_modules/readline2/node_modules/ansi-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", + "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readline2/node_modules/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha1-qSGZYKbV1dBGWXruUSUsZlX3F34=", + "dev": true + }, + "node_modules/readline2/node_modules/strip-ansi": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", + "integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=", + "dev": true, + "dependencies": { + "ansi-regex": "^1.0.0" + }, + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", + "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "dev": true, + "dependencies": { + "minimatch": "3.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redent/node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "node_modules/regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "dependencies": { + "is-equal-shallow": "^0.1.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", + "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", + "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/remark": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", + "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==", + "dev": true, + "dependencies": { + "remark-parse": "^9.0.0", + "remark-stringify": "^9.0.0", + "unified": "^9.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-1.0.0.tgz", + "integrity": "sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==", + "dev": true, + "dependencies": { + "mdast-util-gfm": "^0.1.0", + "micromark-extension-gfm": "^0.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-html": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-13.0.1.tgz", + "integrity": "sha512-K5KQCXWVz+harnyC+UVM/J9eJWCgjYRqFeZoZf2NgP0iFbuuw/RgMZv3MA34b/OEpGnstl3oiOUtZzD3tJ+CBw==", + "dev": true, + "dependencies": { + "hast-util-sanitize": "^3.0.0", + "hast-util-to-html": "^7.0.0", + "mdast-util-to-hast": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", + "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", + "dev": true, + "dependencies": { + "mdast-util-from-markdown": "^0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-reference-links": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-5.0.0.tgz", + "integrity": "sha512-oSIo6lfDyG/1yYl2jPZNXmD9dgyPxp07mSd7snJagVMsDU6NRlD8i54MwHWUgMoOHTs8lIKPkwaUok/tbr5syQ==", + "dev": true, + "dependencies": { + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", + "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", + "dev": true, + "dependencies": { + "mdast-util-to-markdown": "^0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-toc": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-7.2.0.tgz", + "integrity": "sha512-ppHepvpbg7j5kPFmU5rzDC4k2GTcPDvWcxXyr/7BZzO1cBSPk0stKtEJdsgAyw2WHKPGxadcHIZRjb2/sHxjkg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.3", + "mdast-util-toc": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "dependencies": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-bom-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/remove-bom-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/remove-bom-stream/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "dependencies": { + "is-finite": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replacestream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz", + "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.3", + "object-assign": "^4.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/replacestream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/replacestream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-progress": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-0.3.1.tgz", + "integrity": "sha1-ByHBBdipasayzossia4tXs/Pazo=", + "dev": true, + "dependencies": { + "throttleit": "~0.0.2" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "dependencies": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-uncached/node_modules/resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "node_modules/requizzle": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.1.2.tgz", + "integrity": "sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA==", + "dev": true + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "dependencies": { + "value-or-function": "^3.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/response-stream": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/response-stream/-/response-stream-0.0.0.tgz", + "integrity": "sha1-2ksXzHaEyYyWK+tNlfZoyNytCdU=", + "dev": true + }, + "node_modules/responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dev": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + } + }, + "node_modules/resq": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.10.0.tgz", + "integrity": "sha512-hCUd0xMalqtPDz4jXIqs0M5Wnv/LZXN8h7unFOo4/nvExT9dDPbhwd3udRxLlp0HgBnHcV009UlduE9NZi7A6w==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^2.0.1" + } + }, + "node_modules/resq/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/rewire": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-2.5.2.tgz", + "integrity": "sha1-ZCfee3/u+n02QBUH62SlOFvFjcc=", + "dev": true + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rfile": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rfile/-/rfile-1.0.0.tgz", + "integrity": "sha1-WXCM+Qyh50xUw8/Fw2/bmBBDUmE=", + "dev": true, + "dependencies": { + "callsite": "~1.0.0", + "resolve": "~0.3.0" + } + }, + "node_modules/rfile/node_modules/resolve": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.3.1.tgz", + "integrity": "sha1-NMY0R8ZkxwWY0cmxJvxDsqJDEKQ=", + "dev": true + }, + "node_modules/rgb2hex": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.5.tgz", + "integrity": "sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==", + "dev": true + }, + "node_modules/right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "dependencies": { + "align-text": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", + "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", + "dev": true, + "dependencies": { + "glob": "^7.0.5" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/ruglify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ruglify/-/ruglify-1.0.0.tgz", + "integrity": "sha1-3Ikw4qlUSidDAcyZcldMDQmGtnU=", + "dev": true, + "dependencies": { + "rfile": "~1.0", + "uglify-js": "~2.2" + } + }, + "node_modules/ruglify/node_modules/optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "dev": true, + "dependencies": { + "wordwrap": "~0.0.2" + } + }, + "node_modules/ruglify/node_modules/source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ruglify/node_modules/uglify-js": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", + "integrity": "sha1-puAqcNg5eSuXgEiLe4sYTAlcmcc=", + "dev": true, + "dependencies": { + "optimist": "~0.3.5", + "source-map": "~0.1.7" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ruglify/node_modules/wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rx": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz", + "integrity": "sha1-Ia3H2A8CACr1Da6X/Z2/JIdV9WY=", + "dev": true + }, + "node_modules/rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "node_modules/rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "dependencies": { + "rx-lite": "*" + } + }, + "node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", + "dev": true + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "deprecated": "This package has been deprecated in favour of @sinonjs/samsam", + "dev": true + }, + "node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/script-injector": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/script-injector/-/script-injector-0.1.7.tgz", + "integrity": "sha1-ttzO4jcpezev9XWvl/p5FSWo194=", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "through": "~2.3.4", + "trumpet": "~1.6.3" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "dependencies": { + "sver-compat": "^1.5.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", + "dev": true + }, + "node_modules/shasum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true, + "dependencies": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-0.0.1.tgz", + "integrity": "sha1-GkEZbzwDM8SCMjWT1ohuzxU92YY=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shelljs-nodecli": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shelljs-nodecli/-/shelljs-nodecli-0.1.1.tgz", + "integrity": "sha1-8b61VjdUxHXQb30IM+D2B15GLTs=", + "dev": true, + "dependencies": { + "shelljs": "~0.2" + } + }, + "node_modules/shelljs-nodecli/node_modules/shelljs": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.2.6.tgz", + "integrity": "sha1-kEktcv/MgVmXa6umL7D2iE8MM3g=", + "dev": true, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "node_modules/sinon": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", + "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@sinonjs/formatio": "^2.0.0", + "diff": "^3.1.0", + "lodash.get": "^4.4.2", + "lolex": "^2.2.0", + "nise": "^1.2.0", + "supports-color": "^5.1.0", + "type-detect": "^4.0.5" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sntp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", + "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", + "deprecated": "This module moved to @hapi/sntp. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dev": true, + "optional": true, + "dependencies": { + "hoek": "0.9.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/socket.io": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.2.tgz", + "integrity": "sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.0", + "@types/cors": "^2.8.8", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.1", + "engine.io": "~4.1.0", + "socket.io-adapter": "~2.1.0", + "socket.io-parser": "~4.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz", + "integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==", + "dev": true + }, + "node_modules/socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "dev": true, + "dependencies": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true, + "optional": true + }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", + "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", + "dev": true + }, + "node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/sse-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/sse-stream/-/sse-stream-0.0.4.tgz", + "integrity": "sha1-gdov4VxUAOaCSN89HQzgIHuH/x8=", + "dev": true, + "dependencies": { + "through": "~2.2.7" + } + }, + "node_modules/sse-stream/node_modules/through": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/through/-/through-2.2.7.tgz", + "integrity": "sha1-bo4hIAGR1OtqmfbwEN9Gqhxusr0=", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/standard-version": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.3.0.tgz", + "integrity": "sha512-cYxxKXhYfI3S9+CA84HmrJa9B88H56V5FQ302iFF2TNwJukJCNoU8FgWt+11YtwKFXRkQQFpepC2QOF7aDq2Ow==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "conventional-changelog": "3.1.24", + "conventional-changelog-config-spec": "2.1.0", + "conventional-changelog-conventionalcommits": "4.5.0", + "conventional-recommended-bump": "6.1.0", + "detect-indent": "^6.0.0", + "detect-newline": "^3.1.0", + "dotgitignore": "^2.1.0", + "figures": "^3.1.0", + "find-up": "^5.0.0", + "fs-access": "^1.0.1", + "git-semver-tags": "^4.0.0", + "semver": "^7.1.1", + "stringify-package": "^1.0.1", + "yargs": "^16.0.0" + }, + "bin": { + "standard-version": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/standard-version/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/standard-version/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/standard-version/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/standard-version/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/standard-version/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/standard-version/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/standard-version/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/standard-version/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/stream-array/-/stream-array-1.1.2.tgz", + "integrity": "sha1-nl9zRfITfDDuO0mLkRToC1K7frU=", + "dev": true, + "dependencies": { + "readable-stream": "~2.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-array/node_modules/process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "node_modules/stream-array/node_modules/readable-stream": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz", + "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=", + "dev": true, + "dependencies": { + "buffer-shims": "^1.0.0", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stream-array/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stream-browserify/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/stream-buffers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", + "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1" + } + }, + "node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "dependencies": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-combiner2/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stream-combiner2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "node_modules/stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/stream-http/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stream-http/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/stream-splicer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-1.3.2.tgz", + "integrity": "sha1-PARBvhW5v04iYnXm3IOWR0VUZmE=", + "dev": true, + "dependencies": { + "indexof": "0.0.1", + "inherits": "^2.0.1", + "isarray": "~0.0.1", + "readable-stream": "^1.1.13-1", + "readable-wrap": "^1.0.0", + "through2": "^1.0.0" + } + }, + "node_modules/stream-splicer/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/stream-splicer/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/stream-splicer/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/stream-splicer/node_modules/through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "dependencies": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/streamroller": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", + "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", + "dev": true, + "dependencies": { + "date-format": "^2.1.0", + "debug": "^4.1.1", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/streamroller/node_modules/date-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/streamroller/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/streamroller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/streamroller/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/streamroller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=", + "dev": true, + "optional": true + }, + "node_modules/string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-entities": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.1.0.tgz", + "integrity": "sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg==", + "dev": true, + "dependencies": { + "character-entities-html4": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-package": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", + "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", + "dev": true + }, + "node_modules/stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", + "dev": true, + "optional": true + }, + "node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "dependencies": { + "minimist": "^1.1.0" + } + }, + "node_modules/suffix": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/suffix/-/suffix-0.1.1.tgz", + "integrity": "sha1-zFgjFkag7xEC95R47zqSSP2chC8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "dependencies": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "dev": true, + "dependencies": { + "acorn-node": "^1.2.0" + } + }, + "node_modules/table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", + "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, + "node_modules/tapable": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", + "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/temp-fs": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", + "integrity": "sha1-gHFzBDeHByDpQxUy/igUNk+IA9c=", + "dev": true, + "dependencies": { + "rimraf": "~2.5.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ternary-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-3.0.0.tgz", + "integrity": "sha512-oIzdi+UL/JdktkT+7KU5tSIQjj8pbShj3OASuvDEhm0NT5lppsm7aXWAmAq4/QMaBIyfuEcNLbAQA+HpaISobQ==", + "dev": true, + "dependencies": { + "duplexify": "^4.1.1", + "fork-stream": "^0.0.4", + "merge-stream": "^2.0.0", + "through2": "^3.0.1" + } + }, + "node_modules/ternary-stream/node_modules/duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ternary-stream/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/ternary-stream/node_modules/through2/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/terser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.4.0.tgz", + "integrity": "sha512-3dZunFLbCJis9TAF2VnX+VrQLctRUmt1p3W2kCsJuZE4ZgWqh//+1MZ62EanewrqKoUf4zIaDGZAvml4UDc0OQ==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/textextensions": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz", + "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/throttleit": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", + "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "dependencies": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "node_modules/through2-filter/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2-filter/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/through2-filter/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/timed-out": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", + "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dev": true, + "dependencies": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, + "node_modules/tiny-hashes": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tiny-hashes/-/tiny-hashes-1.0.1.tgz", + "integrity": "sha512-knIN5zj4fl7kW4EBU5sLP20DWUvi/rVouvJezV0UAym2DkQaqm365Nyc8F3QEiOvunNDMxR8UhcXd1d5g+Wg1g==" + }, + "node_modules/tiny-lr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "dev": true, + "dependencies": { + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" + } + }, + "node_modules/tiny-lr/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/tiny-lr/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", + "deprecated": "to-iso-string has been deprecated, use @segment/to-iso-string instead.", + "dev": true + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "dependencies": { + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/to-through/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/to-through/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/to-through/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/treeify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.0.1.tgz", + "integrity": "sha1-abPNAiAioWhCTnz6HO1EyTnT6y8=", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/trim-off-newlines": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.3.tgz", + "integrity": "sha512-kh6Tu6GbeSNMGfrrZh6Bb/4ZEHV1QlB4xNDBeog8Y9/QwFlKTRyWvY3Fs9tRDAMZliVUwieMgEdIeL/FtqjkJg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trumpet": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/trumpet/-/trumpet-1.6.6.tgz", + "integrity": "sha1-6Oq9R8lYtWChIG2bm2yEtH6oA+Q=", + "dev": true, + "dependencies": { + "duplexer2": "~0.0.2", + "html-select": "^2.3.5", + "html-tokenize": "^1.1.1", + "inherits": "^2.0.0", + "readable-stream": "^1.0.27-1", + "through2": "^1.0.0" + } + }, + "node_modules/trumpet/node_modules/duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "dependencies": { + "readable-stream": "~1.1.9" + } + }, + "node_modules/trumpet/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/trumpet/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/trumpet/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/trumpet/node_modules/through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "dependencies": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", + "dev": true + }, + "node_modules/tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "node_modules/typescript-compare": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", + "integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==", + "dependencies": { + "typescript-logic": "^0.0.0" + } + }, + "node_modules/typescript-logic": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz", + "integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q==" + }, + "node_modules/typescript-tuple": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz", + "integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==", + "dependencies": { + "typescript-compare": "^0.0.2" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.28", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", + "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "node_modules/uglify-js": { + "version": "3.13.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.9.tgz", + "integrity": "sha512-wZbyTQ1w6Y7fHdt8sJnHfSIuWeDgk6B5rCb4E/AM6QNNPbOMIZph21PW5dRB3h7Df0GszN+t7RuUH6sWK5bF0g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true + }, + "node_modules/uglifyjs-webpack-plugin": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", + "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "source-map": "^0.5.6", + "uglify-js": "^2.8.29", + "webpack-sources": "^1.0.1" + }, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + }, + "peerDependencies": { + "webpack": "^1.9 || ^2 || ^2.1.0-beta || ^2.2.0-rc || ^3.0.0" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "dependencies": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "dependencies": { + "source-map": "~0.5.1", + "yargs": "~3.10.0" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + }, + "optionalDependencies": { + "uglify-to-browserify": "~1.0.0" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "dependencies": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + }, + "node_modules/umd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/umd/-/umd-2.1.0.tgz", + "integrity": "sha1-SmMHt2LxfwLSAbX6FU5nM5bCY88=", + "dev": true, + "dependencies": { + "rfile": "~1.0.0", + "ruglify": "~1.0.0", + "through": "~2.3.4", + "uglify-js": "~2.4.0" + }, + "bin": { + "umd": "bin/cli.js" + } + }, + "node_modules/umd/node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "node_modules/umd/node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/umd/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/umd/node_modules/source-map": { + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz", + "integrity": "sha1-p8/omux7FoLDsZjQrPtH19CQVms=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/umd/node_modules/uglify-js": { + "version": "2.4.24", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.24.tgz", + "integrity": "sha1-+tV1XB4Vd2WLsG/5q25UjJW+vW4=", + "dev": true, + "dependencies": { + "async": "~0.2.6", + "source-map": "0.1.34", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.5.4" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/umd/node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/umd/node_modules/yargs": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.5.4.tgz", + "integrity": "sha1-2K/49mXpTDS9JZvevRv68N3TU2E=", + "dev": true, + "dependencies": { + "camelcase": "^1.0.2", + "decamelize": "^1.0.0", + "window-size": "0.1.0", + "wordwrap": "0.0.2" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/underscore": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz", + "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==", + "dev": true + }, + "node_modules/underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/undertaker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/undertaker/node_modules/fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.1.tgz", + "integrity": "sha512-juWjuI8Z4xFg8pJbnEZ41b5xjGUWGHqXALmBZ3FC3WX0PIx1CZBIIJ6mXbYMcf6Yw4Fi0rFUTA1cdz/BglbOhA==", + "dev": true, + "dependencies": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "dependencies": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "node_modules/unist-builder": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", + "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-generated": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz", + "integrity": "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", + "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unzipper": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.9.15.tgz", + "integrity": "sha512-2aaUvO4RAeHDvOCuEtth7jrHFaCKTSXPqUkXwADaLBzGbgZGzUDccoEdJ5lW+3RmfpOZYNx0Rw6F6PUzM6caIA==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=", + "dev": true + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url-join": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", + "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", + "dev": true + }, + "node_modules/url-parse": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", + "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true, + "bin": { + "user-home": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/util-extend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", + "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vfile": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-reporter": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-6.0.2.tgz", + "integrity": "sha512-GN2bH2gs4eLnw/4jPSgfBjo+XCuvnX9elHICJZjVD4+NM0nsUrMTvdjGY5Sc/XG69XVTgLwj7hknQVc6M9FukA==", + "dev": true, + "dependencies": { + "repeat-string": "^1.5.0", + "string-width": "^4.0.0", + "supports-color": "^6.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-sort": "^2.1.2", + "vfile-statistics": "^1.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-reporter/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/vfile-sort": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-2.2.2.tgz", + "integrity": "sha512-tAyUqD2R1l/7Rn7ixdGkhXLD3zsg+XLAeUDUhXearjfIcpL1Hcsj5hHpCoy/gvfK/Ws61+e972fm0F7up7hfYA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-statistics": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-1.1.4.tgz", + "integrity": "sha512-lXhElVO0Rq3frgPvFBwahmed3X03vjPF8OcjKMy8+F1xU/3Q3QU3tKEDp743SFtb74PdF0UWpxPvtOP0GCLheA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile/node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "dependencies": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/vinyl-fs/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/vinyl-fs/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "dependencies": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "dependencies": { + "source-map": "^0.5.1" + } + }, + "node_modules/vinyl/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", + "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==", + "dev": true, + "optional": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, + "node_modules/walk": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.14.tgz", + "integrity": "sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg==", + "dev": true, + "dependencies": { + "foreachasync": "^3.0.0" + } + }, + "node_modules/watchpack": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + }, + "optionalDependencies": { + "chokidar": "^3.4.1", + "watchpack-chokidar2": "^2.0.1" + } + }, + "node_modules/watchpack-chokidar2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "dev": true, + "optional": true, + "dependencies": { + "chokidar": "^2.1.8" + } + }, + "node_modules/watchpack-chokidar2/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "optional": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dev": true, + "optional": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "optional": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "optional": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "optional": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "optional": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "optional": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "optional": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/watchpack-chokidar2/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/watchpack-chokidar2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "optional": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webdriver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.7.4.tgz", + "integrity": "sha512-bE6/A+OYb040GZ1MiuZebc8bOOYm797dmqEfmj6aoEQ4BMy1juiFlzCzeBzAlPrq33qPa8/CSYfH7rnkB3RRwg==", + "dev": true, + "dependencies": { + "@types/node": "^14.14.31", + "@wdio/config": "7.7.3", + "@wdio/logger": "7.7.0", + "@wdio/protocols": "7.7.4", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", + "got": "^11.0.2", + "lodash.merge": "^4.6.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriver/node_modules/@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true + }, + "node_modules/webdriver/node_modules/@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriver/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/webdriver/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/webdriver/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/webdriver/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/webdriver/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/webdriver/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webdriverio": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.7.4.tgz", + "integrity": "sha512-VSWRj2mmvA8WbideFAYb5BMWPkBCJ7gJHhYrUSibTrMHKreRtX++cw/oGxxowy9/pTHsAW6OxlnaDxFL5Gt08A==", + "dev": true, + "dependencies": { + "@types/aria-query": "^4.2.1", + "@types/node": "^14.14.31", + "@wdio/config": "7.7.3", + "@wdio/logger": "7.7.0", + "@wdio/protocols": "7.7.4", + "@wdio/repl": "7.7.3", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", + "archiver": "^5.0.0", + "aria-query": "^4.2.2", + "atob": "^2.1.2", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools": "7.7.4", + "devtools-protocol": "^0.0.892017", + "fs-extra": "^10.0.0", + "get-port": "^5.1.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^9.1.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^8.0.0", + "webdriver": "7.7.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriverio/node_modules/@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true + }, + "node_modules/webdriverio/node_modules/@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriverio/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/webdriverio/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/webdriverio/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/webdriverio/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/webdriverio/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/webdriverio/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.12.0.tgz", + "integrity": "sha512-Sw7MdIIOv/nkzPzee4o0EdvCuPmxT98+vVpIvwtcwcF1Q4SDSNp92vwcKc4REe7NItH9f1S4ra9FuQ7yuYZ8bQ==", + "dev": true, + "dependencies": { + "acorn": "^5.0.0", + "acorn-dynamic-import": "^2.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "async": "^2.1.2", + "enhanced-resolve": "^3.4.0", + "escope": "^3.6.0", + "interpret": "^1.0.0", + "json-loader": "^0.5.4", + "json5": "^0.5.1", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "mkdirp": "~0.5.0", + "node-libs-browser": "^2.0.0", + "source-map": "^0.5.3", + "supports-color": "^4.2.1", + "tapable": "^0.2.7", + "uglifyjs-webpack-plugin": "^0.4.6", + "watchpack": "^1.4.0", + "webpack-sources": "^1.0.1", + "yargs": "^8.0.2" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz", + "integrity": "sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1", + "bfj": "^6.1.1", + "chalk": "^2.4.1", + "commander": "^2.18.0", + "ejs": "^2.6.1", + "express": "^4.16.3", + "filesize": "^3.6.1", + "gzip-size": "^5.0.0", + "lodash": "^4.17.19", + "mkdirp": "^0.5.1", + "opener": "^1.5.1", + "ws": "^6.0.0" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 6.14.4" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", + "dev": true, + "hasInstallScript": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "dev": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/webpack-core": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", + "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", + "dev": true, + "dependencies": { + "source-list-map": "~0.1.7", + "source-map": "~0.4.1" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/webpack-core/node_modules/source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "node_modules/webpack-core/node_modules/source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-2.0.6.tgz", + "integrity": "sha512-tj5LLD9r4tDuRIDa5Mu9lnY2qBBehAITv6A9irqXhw/HQquZgTx3BCd57zYbU2gMDnncA49ufK2qVQSbaKJwOw==", + "dev": true, + "dependencies": { + "loud-rejection": "^1.6.0", + "memory-fs": "~0.4.1", + "mime": "^2.1.0", + "path-is-absolute": "^1.0.0", + "range-parser": "^1.0.3", + "url-join": "^2.0.2", + "webpack-log": "^1.0.1" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "webpack": "^2.2.0 || ^3.0.0 || ^4.0.0-alpha || ^4.0.0-beta || ^4.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack-log": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-1.2.0.tgz", + "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", + "dev": true, + "dependencies": { + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "loglevelnext": "^1.0.1", + "uuid": "^3.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-log/node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-log/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/webpack-sources/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/webpack-stream/-/webpack-stream-3.2.0.tgz", + "integrity": "sha1-Oh0WD7EdQXJ7fObzL3IkZPmLIYY=", + "dev": true, + "dependencies": { + "gulp-util": "^3.0.7", + "lodash.clone": "^4.3.2", + "lodash.some": "^4.2.2", + "memory-fs": "^0.3.0", + "through": "^2.3.8", + "vinyl": "^1.1.0", + "webpack": "^1.12.9" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack-stream/node_modules/anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "dependencies": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "node_modules/webpack-stream/node_modules/arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/webpack-stream/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "dependencies": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/browserify-aes": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-0.4.0.tgz", + "integrity": "sha1-BnFJtmjfMcS1hTPgLQHoBthgjiw=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1" + } + }, + "node_modules/webpack-stream/node_modules/browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "dependencies": { + "pako": "~0.2.0" + } + }, + "node_modules/webpack-stream/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/webpack-stream/node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dev": true, + "dependencies": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + }, + "optionalDependencies": { + "fsevents": "^1.0.0" + } + }, + "node_modules/webpack-stream/node_modules/cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "dependencies": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "node_modules/webpack-stream/node_modules/clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/crypto-browserify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.3.0.tgz", + "integrity": "sha1-ufx1u0oO1h3PHNXa6W6zDJw+UGw=", + "dev": true, + "dependencies": { + "browserify-aes": "0.4.0", + "pbkdf2-compat": "2.0.1", + "ripemd160": "0.2.0", + "sha.js": "2.2.6" + }, + "engines": { + "node": "*" + } + }, + "node_modules/webpack-stream/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/webpack-stream/node_modules/enhanced-resolve": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", + "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.2.0", + "tapable": "^0.1.8" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/webpack-stream/node_modules/enhanced-resolve/node_modules/memory-fs": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", + "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/webpack-stream/node_modules/expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "dependencies": { + "is-posix-bracket": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/webpack-stream/node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/webpack-stream/node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/interpret": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-0.6.6.tgz", + "integrity": "sha1-/s16GOfOXKar+5U+H4YhOknxYls=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/is-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/webpack-stream/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "dependencies": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + }, + "node_modules/webpack-stream/node_modules/memory-fs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.3.0.tgz", + "integrity": "sha1-e8xrYp46Q+hx1+Kaymrop/FcuyA=", + "dev": true, + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "node_modules/webpack-stream/node_modules/micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "dependencies": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/webpack-stream/node_modules/node-libs-browser": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-0.7.0.tgz", + "integrity": "sha1-PicsCBnjCJNeJmdECNevDhSRuDs=", + "dev": true, + "dependencies": { + "assert": "^1.1.1", + "browserify-zlib": "^0.1.4", + "buffer": "^4.9.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "3.3.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", + "https-browserify": "0.0.1", + "os-browserify": "^0.2.0", + "path-browserify": "0.0.0", + "process": "^0.11.0", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.0.5", + "stream-browserify": "^2.0.1", + "stream-http": "^2.3.1", + "string_decoder": "^0.10.25", + "timers-browserify": "^2.0.2", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.10.3", + "vm-browserify": "0.0.4" + } + }, + "node_modules/webpack-stream/node_modules/node-libs-browser/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/os-browserify": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz", + "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/webpack-stream/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/expand-brackets/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/readdirp/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/webpack-stream/node_modules/ripemd160": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-0.2.0.tgz", + "integrity": "sha1-K/GYveFnys+lHAqSjoS2i74XH84=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/sha.js": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.2.6.tgz", + "integrity": "sha1-F93t3F9yL7ZlAWWIlUYZd4ZzFbo=", + "dev": true, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/webpack-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/webpack-stream/node_modules/supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webpack-stream/node_modules/tapable": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", + "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/webpack-stream/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-stream/node_modules/uglify-js": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.7.5.tgz", + "integrity": "sha1-RhLAx7qu4rp8SH3kkErhIgefLKg=", + "dev": true, + "dependencies": { + "async": "~0.2.6", + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webpack-stream/node_modules/uglify-js/node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/webpack-stream/node_modules/vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "dependencies": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/webpack-stream/node_modules/vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "dependencies": { + "indexof": "0.0.1" + } + }, + "node_modules/webpack-stream/node_modules/watchpack": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-0.2.9.tgz", + "integrity": "sha1-Yuqkq15bo1/fwBgnVibjwPXj+ws=", + "dev": true, + "dependencies": { + "async": "^0.9.0", + "chokidar": "^1.0.0", + "graceful-fs": "^4.1.2" + } + }, + "node_modules/webpack-stream/node_modules/watchpack/node_modules/async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "node_modules/webpack-stream/node_modules/webpack": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-1.15.0.tgz", + "integrity": "sha1-T/MfU9sDM55VFkqdRo7gMklo/pg=", + "dev": true, + "dependencies": { + "acorn": "^3.0.0", + "async": "^1.3.0", + "clone": "^1.0.2", + "enhanced-resolve": "~0.9.0", + "interpret": "^0.6.4", + "loader-utils": "^0.2.11", + "memory-fs": "~0.3.0", + "mkdirp": "~0.5.0", + "node-libs-browser": "^0.7.0", + "optimist": "~0.6.0", + "supports-color": "^3.1.0", + "tapable": "~0.1.8", + "uglify-js": "~2.7.3", + "watchpack": "^0.2.1", + "webpack-core": "~0.6.9" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/webpack-stream/node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack-stream/node_modules/yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "dependencies": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + }, + "node_modules/webpack/node_modules/acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/webpack/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/webpack/node_modules/cliui/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/webpack/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "node_modules/webpack/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/webpack/node_modules/load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack/node_modules/loader-utils/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/webpack/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/webpack/node_modules/os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "dependencies": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "dependencies": { + "pify": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "dependencies": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "node_modules/webpack/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/string-width/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "dependencies": { + "has-flag": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/webpack/node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/wrap-ansi/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "dev": true + }, + "node_modules/webpack/node_modules/yargs": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", + "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + } + }, + "node_modules/webpack/node_modules/yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/which-typed-array": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", + "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.2", + "call-bind": "^1.0.0", + "es-abstract": "^1.18.0-next.1", + "foreach": "^2.0.5", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-typed-array": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "node_modules/workerpool": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.4.tgz", + "integrity": "sha512-jGWPzsUqzkow8HoAvqaPWTUPCrlPJaJ5tY8Iz7n1uCz3tTp6s3CDG0FF1NsX42WNlkRSW6Mr+CDZGnNoSsKa7g==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/write/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ws": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz", + "integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-escape": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.0.0.tgz", + "integrity": "sha1-AJY9aXsq3wwYXE4E5zF0upsojrI=", + "dev": true + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "node_modules/yargs": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.3.3.tgz", + "integrity": "sha1-BU3oth8i7v23IHBZ6u+da4P7kxo=", + "dev": true + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yarn-install": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yarn-install/-/yarn-install-1.0.0.tgz", + "integrity": "sha1-V/RQULgu/VcYKzlzxUqgXLXSUjA=", + "dev": true, + "dependencies": { + "cac": "^3.0.3", + "chalk": "^1.1.3", + "cross-spawn": "^4.0.2" + }, + "bin": { + "yarn-install": "bin/yarn-install.js", + "yarn-remove": "bin/yarn-remove.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yarn-install/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yarn-install/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yarn-install/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yarn-install/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yarn-install/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "dev": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "plugins/eslint": { + "version": "1.0.0", + "integrity": "sha1-riJvuPDcgkOlLBywB85/YPnZ+PA=", + "dev": true, + "dependencies": { + "chalk": "^1.0.0", + "concat-stream": "^1.4.6", + "debug": "^2.1.1", + "doctrine": "^0.6.2", + "escape-string-regexp": "^1.0.2", + "escope": "^3.2.0", + "espree": "^2.2.0", + "estraverse": "^4.1.0", + "estraverse-fb": "^1.3.1", + "globals": "^8.2.0", + "inquirer": "^0.8.2", + "is-my-json-valid": "^2.10.0", + "js-yaml": "^3.2.5", + "lodash.clonedeep": "^3.0.1", + "lodash.merge": "^3.3.2", + "lodash.omit": "^3.1.0", + "minimatch": "^2.0.1", + "mkdirp": "^0.5.0", + "object-assign": "^2.0.0", + "optionator": "^0.5.0", + "path-is-absolute": "^1.0.0", + "path-is-inside": "^1.0.1", + "strip-json-comments": "~1.0.1", + "text-table": "~0.2.0", + "user-home": "^1.0.0", + "xml-escape": "~1.0.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "devDependencies": { + "beefy": "^1.0.0", + "brfs": "0.0.9", + "browserify": "^8.1.3", + "chai": "^1.9.1", + "coveralls": "2.11.2", + "dateformat": "^1.0.8", + "esprima": "^2.4.1", + "esprima-fb": "^10001.1.0-dev-harmony-fb", + "gh-got": "^1.0.3", + "istanbul": "^0.3.5", + "jsdoc": "^3.3.0-beta1", + "jsonlint": "^1.6.2", + "markdownlint": "^0.0.6", + "mocha": "^2.1.0", + "mocha-phantomjs": "3.6.0", + "npm-license": "^0.2.3", + "phantomjs": "1.9.7-15", + "proxyquire": "^1.0.0", + "rewire": "^2.3.4", + "semver": "^4.1.0", + "shelljs": "^0.3.0", + "shelljs-nodecli": "~0.1.0", + "sinon": "1.14.1", + "through": "^2.3.6" + }, + "engines": { + "node": ">=0.10" + } + }, + "plugins/eslint/node_modules/ansi-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", + "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "plugins/eslint/node_modules/asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.9" + } + }, + "plugins/eslint/node_modules/assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "plugins/eslint/node_modules/assertion-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", + "dev": true, + "engines": { + "node": "*" + } + }, + "plugins/eslint/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "plugins/eslint/node_modules/aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "plugins/eslint/node_modules/camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "dependencies": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/chai": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-1.10.0.tgz", + "integrity": "sha1-5AMcyHZURhp1lD5aNatG6vOcHrk=", + "dev": true, + "dependencies": { + "assertion-error": "1.0.0", + "deep-eql": "0.1.3" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "plugins/eslint/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/cli-width": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz", + "integrity": "sha1-pNKT72frt7iNSk1CwMzwDE0eNm0=", + "dev": true + }, + "plugins/eslint/node_modules/combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "optional": true, + "dependencies": { + "delayed-stream": "0.0.5" + }, + "engines": { + "node": ">= 0.8" + } + }, + "plugins/eslint/node_modules/commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true, + "engines": { + "node": ">= 0.6.x" + } + }, + "plugins/eslint/node_modules/coveralls": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-2.11.2.tgz", + "integrity": "sha1-1NmCAWyy+dqJ13qyBNhqhTfmoS0=", + "dev": true, + "dependencies": { + "js-yaml": "3.0.1", + "lcov-parse": "0.0.6", + "log-driver": "1.2.4", + "request": "2.40.0" + }, + "bin": { + "coveralls": "bin/coveralls.js" + }, + "engines": { + "node": ">=0.8.6" + } + }, + "plugins/eslint/node_modules/coveralls/node_modules/argparse": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "dev": true, + "dependencies": { + "underscore": "~1.7.0", + "underscore.string": "~2.4.0" + } + }, + "plugins/eslint/node_modules/coveralls/node_modules/esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "plugins/eslint/node_modules/coveralls/node_modules/js-yaml": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.0.1.tgz", + "integrity": "sha1-dkBf6lvOMPyPQF1Ixtyn8KMsav4=", + "dev": true, + "dependencies": { + "argparse": "~ 0.1.11", + "esprima": "~ 1.0.2" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + }, + "engines": { + "node": ">= 0.6.0" + } + }, + "plugins/eslint/node_modules/dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "dependencies": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + }, + "bin": { + "dateformat": "bin/cli.js" + }, + "engines": { + "node": "*" + } + }, + "plugins/eslint/node_modules/debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "dependencies": { + "ms": "0.7.1" + } + }, + "plugins/eslint/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "dependencies": { + "type-detect": "0.1.1" + }, + "engines": { + "node": "*" + } + }, + "plugins/eslint/node_modules/delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "plugins/eslint/node_modules/diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "plugins/eslint/node_modules/doctrine": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.6.4.tgz", + "integrity": "sha1-gUKEkalC7xiwSSBW7aOADu5X1h0=", + "dev": true, + "dependencies": { + "esutils": "^1.1.6", + "isarray": "0.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/escodegen": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.1.tgz", + "integrity": "sha1-MOz89mypjcZ80v0WKr626vqM5vw=", + "dev": true, + "dependencies": { + "esprima": "^1.2.2", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.5.0" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=0.12.0" + }, + "optionalDependencies": { + "source-map": "~0.2.0" + } + }, + "plugins/eslint/node_modules/escodegen/node_modules/esprima": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", + "integrity": "sha1-CZNQL+r2aBODJXVvMPmlH+7sEek=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "plugins/eslint/node_modules/escodegen/node_modules/estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/escodegen/node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/espree": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/espree/-/espree-2.2.5.tgz", + "integrity": "sha1-32kbkxCIlAKuspzAZnCMVmkLhUs=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/esutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz", + "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/fast-levenshtein": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz", + "integrity": "sha1-AXjc3uAjuSkFGTrwlZ6KdjnP3Lk=", + "dev": true + }, + "plugins/eslint/node_modules/figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/figures/node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=", + "dev": true, + "engines": { + "node": "*" + } + }, + "plugins/eslint/node_modules/form-data": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", + "dev": true, + "optional": true, + "dependencies": { + "async": "~0.9.0", + "combined-stream": "~0.0.4", + "mime": "~1.2.11" + }, + "engines": { + "node": ">= 0.8" + } + }, + "plugins/eslint/node_modules/form-data/node_modules/async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true, + "optional": true + }, + "plugins/eslint/node_modules/glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "dependencies": { + "inherits": "2", + "minimatch": "0.3" + }, + "engines": { + "node": "*" + } + }, + "plugins/eslint/node_modules/glob/node_modules/minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "dependencies": { + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "engines": { + "node": "*" + } + }, + "plugins/eslint/node_modules/globals": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz", + "integrity": "sha1-k9SmK9ysOM+vr8R9awNHaMsP/LQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "plugins/eslint/node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "dev": true, + "optional": true, + "dependencies": { + "asn1": "0.1.11", + "assert-plus": "^0.1.5", + "ctype": "0.5.3" + }, + "engines": { + "node": ">=0.8" + } + }, + "plugins/eslint/node_modules/indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "dependencies": { + "repeating": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/inquirer": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz", + "integrity": "sha1-29dAz2yjtzEpamPOb22WGFHzNt8=", + "dev": true, + "dependencies": { + "ansi-regex": "^1.1.1", + "chalk": "^1.0.0", + "cli-width": "^1.0.1", + "figures": "^1.3.5", + "lodash": "^3.3.1", + "readline2": "^0.1.1", + "rx": "^2.4.3", + "through": "^2.3.6" + } + }, + "plugins/eslint/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "plugins/eslint/node_modules/istanbul": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.3.22.tgz", + "integrity": "sha1-PhZNhQIf4ZyYXR8OfvDD4i0BLrY=", + "deprecated": "This module is no longer maintained, try this instead:\n npm i nyc\nVisit https://istanbul.js.org/integrations for other alternatives.", + "dev": true, + "dependencies": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.7.x", + "esprima": "2.5.x", + "fileset": "0.2.x", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "istanbul": "lib/cli.js" + } + }, + "plugins/eslint/node_modules/istanbul/node_modules/esprima": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.5.0.tgz", + "integrity": "sha1-84ekb9NEwbGjm6+MIL+0O20AWMw=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/istanbul/node_modules/supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "plugins/eslint/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "plugins/eslint/node_modules/js-yaml/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "plugins/eslint/node_modules/lcov-parse": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.6.tgz", + "integrity": "sha1-gZ5dqL8HkfnT857qXtGGgYfxEXU=", + "dev": true + }, + "plugins/eslint/node_modules/levn": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz", + "integrity": "sha1-uo0znQykphDjo/FFucr0iAcVUFQ=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.0", + "type-check": "~0.3.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "plugins/eslint/node_modules/lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "plugins/eslint/node_modules/lodash.clonedeep": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz", + "integrity": "sha1-oKHkDYKl6on/WxR7hETtY9koJ9s=", + "dev": true, + "dependencies": { + "lodash._baseclone": "^3.0.0", + "lodash._bindcallback": "^3.0.0" + } + }, + "plugins/eslint/node_modules/lodash.isplainobject": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz", + "integrity": "sha1-moI4rhayAEMpYM1zRlEtASP79MU=", + "dev": true, + "dependencies": { + "lodash._basefor": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.keysin": "^3.0.0" + } + }, + "plugins/eslint/node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "plugins/eslint/node_modules/lodash.merge": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-3.3.2.tgz", + "integrity": "sha1-DZDZPtY3sYeEN7s+IWASYNev6ZQ=", + "dev": true, + "dependencies": { + "lodash._arraycopy": "^3.0.0", + "lodash._arrayeach": "^3.0.0", + "lodash._createassigner": "^3.0.0", + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0", + "lodash.isplainobject": "^3.0.0", + "lodash.istypedarray": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.keysin": "^3.0.0", + "lodash.toplainobject": "^3.0.0" + } + }, + "plugins/eslint/node_modules/log-driver": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.4.tgz", + "integrity": "sha1-LWLX+u9F2KcTQZYaBLB2HsqZz6M=", + "dev": true, + "engines": { + "node": ">=0.8.6" + } + }, + "plugins/eslint/node_modules/lolex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.1.0.tgz", + "integrity": "sha1-Xbu8hQOV51I8dLNYb3+9JibSWxs=", + "dev": true + }, + "plugins/eslint/node_modules/lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "plugins/eslint/node_modules/meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "dependencies": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/meow/node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true, + "optional": true + }, + "plugins/eslint/node_modules/mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "plugins/eslint/node_modules/minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "dependencies": { + "brace-expansion": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "plugins/eslint/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "plugins/eslint/node_modules/mocha": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "dev": true, + "dependencies": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 0.8.x" + } + }, + "plugins/eslint/node_modules/mocha/node_modules/escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "plugins/eslint/node_modules/mocha/node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "plugins/eslint/node_modules/mocha/node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "plugins/eslint/node_modules/mocha/node_modules/supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "dev": true, + "bin": { + "supports-color": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "plugins/eslint/node_modules/oauth-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", + "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "plugins/eslint/node_modules/object-assign": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", + "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/optionator": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz", + "integrity": "sha1-t1qJlaLUF98ltuTjhi9QqohlE2g=", + "dev": true, + "dependencies": { + "deep-is": "~0.1.2", + "fast-levenshtein": "~1.0.0", + "levn": "~0.2.5", + "prelude-ls": "~1.1.1", + "type-check": "~0.3.1", + "wordwrap": "~0.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "plugins/eslint/node_modules/optionator/node_modules/wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "plugins/eslint/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "plugins/eslint/node_modules/qs": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-1.0.2.tgz", + "integrity": "sha1-UKk+K1r2aRwxvOpdrnjubqGQN2g=", + "dev": true + }, + "plugins/eslint/node_modules/redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "dependencies": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/request": { + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.40.0.tgz", + "integrity": "sha1-TdZw9pbx5uhC5mtLXoOTAaub62c=", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "dependencies": { + "forever-agent": "~0.5.0", + "json-stringify-safe": "~5.0.0", + "mime-types": "~1.0.1", + "node-uuid": "~1.4.0", + "qs": "~1.0.0" + }, + "optionalDependencies": { + "aws-sign2": "~0.5.0", + "form-data": "~0.1.0", + "hawk": "1.1.1", + "http-signature": "~0.10.0", + "oauth-sign": "~0.3.0", + "stringstream": "~0.0.4", + "tough-cookie": ">=0.12.0", + "tunnel-agent": "~0.4.0" + } + }, + "plugins/eslint/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "plugins/eslint/node_modules/semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "plugins/eslint/node_modules/shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "dev": true, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "plugins/eslint/node_modules/sinon": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.14.1.tgz", + "integrity": "sha1-2CeXhBkYc0UHyUt6c+P1YJBFeK0=", + "dev": true, + "dependencies": { + "formatio": "1.1.1", + "lolex": "1.1.0", + "util": ">=0.10.3 <1" + }, + "engines": { + "node": ">=0.1.103" + } + }, + "plugins/eslint/node_modules/source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "plugins/eslint/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "dependencies": { + "get-stdin": "^4.0.1" + }, + "bin": { + "strip-indent": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true, + "bin": { + "strip-json-comments": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "plugins/eslint/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "plugins/eslint/node_modules/trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "plugins/eslint/node_modules/tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "plugins/eslint/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "plugins/eslint/node_modules/type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true, + "engines": { + "node": "*" + } + }, + "plugins/eslint/node_modules/underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + }, + "plugins/eslint/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/compat-data": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", + "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==", + "dev": true + }, + "@babel/core": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz", + "integrity": "sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helpers": "^7.14.6", + "@babel/parser": "^7.14.6", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/eslint-parser": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.16.5.tgz", + "integrity": "sha512-mUqYa46lgWqHKQ33Q6LNCGp/wPR3eqOYTUixHFsfrSQqRxH0+WOzca75iEjFr5RDGH1dDz622LaHhLOzOuQRUA==", + "dev": true, + "requires": { + "eslint-scope": "^5.1.1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.0" + } + }, + "@babel/generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", + "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.14.5.tgz", + "integrity": "sha512-YTA/Twn0vBXDVGJuAX6PwW7x5zQei1luDDo2Pl6q1qZ7hVNl0RZrhHCQG/ArGpR29Vl7ETiB8eJyrvpuRp300w==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", + "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz", + "integrity": "sha512-Z6gsfGofTxH/+LQXqYEK45kxmcensbzmk/oi8DmaQytlQCgqNZt9XQF8iqlI/SeXWVjaMNxvYvzaYw+kh42mDg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", + "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "regexpu-core": "^4.7.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz", + "integrity": "sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.14.5.tgz", + "integrity": "sha512-Htb24gnGJdIGT4vnRKMdoXiOIlqOLmdiUYpAQ0mYfgVT/GDm8GOYhgi4GL+hMKrkiPRohO4ts34ELFsGAPQLDQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", + "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", + "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.14.5.tgz", + "integrity": "sha512-rLQKdQU+HYlxBwQIj8dk4/0ENOUEhA/Z0l4hN8BexpvmSMN9oA9EagjnhnDpNsRdWCfjwa4mn/HyBXO9yhQP6A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-wrap-function": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-replace-supers": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", + "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.14.5.tgz", + "integrity": "sha512-dmqZB7mrb94PZSAOYtr+ZN5qt5owZIAgqtoTuqiFbHFtxgEcmQlRJVI+bO++fciBunXtB6MK7HrzrfcAzIz2NQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.14.5.tgz", + "integrity": "sha512-YEdjTCq+LNuNS1WfxsDCNpgXkJaIyqco6DAelTUjT4f2KIWC1nBcaCaSdHTBqQVLnTBexBcVcFhLSU1KnYuePQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helpers": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz", + "integrity": "sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==", + "dev": true, + "requires": { + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", + "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", + "dev": true + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.14.5.tgz", + "integrity": "sha512-ZoJS2XCKPBfTmL122iP6NM9dOg+d4lc9fFk3zxc8iDjvt8Pk4+TlsHSKhIPf6X+L5ORCdBzqMZDjL/WHj7WknQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", + "@babel/plugin-proposal-optional-chaining": "^7.14.5" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz", + "integrity": "sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-remap-async-to-generator": "^7.14.5", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz", + "integrity": "sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.14.5.tgz", + "integrity": "sha512-KBAH5ksEnYHCegqseI5N9skTdxgJdmDoAOc0uXa+4QMYKeZD0w5IARh4FMlTNtaHhbB8v+KzMdTgxMMzsIy6Yg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz", + "integrity": "sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz", + "integrity": "sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz", + "integrity": "sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz", + "integrity": "sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz", + "integrity": "sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.5.tgz", + "integrity": "sha512-yiclALKe0vyZRZE0pS6RXgjUOt87GWv6FYa5zqj15PvhOGFO69R5DusPlgK/1K5dVnCtegTiWu9UaBSrLLJJBg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz", + "integrity": "sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.14.7", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.14.5" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz", + "integrity": "sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz", + "integrity": "sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz", + "integrity": "sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-62EyfyA3WA0mZiF2e2IV9mc9Ghwxcg8YTu8BS4Wss4Y3PY725OmS9M0qLORbJwLqFtGh+jiE4wAmocK2CTUK2Q==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz", + "integrity": "sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz", + "integrity": "sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", + "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-remap-async-to-generator": "^7.14.5" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz", + "integrity": "sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.5.tgz", + "integrity": "sha512-LBYm4ZocNgoCqyxMLoOnwpsmQ18HWTQvql64t3GvMUzLQrNoV1BDG0lNftC8QKYERkZgCCT/7J5xWGObGAyHDw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.5.tgz", + "integrity": "sha512-J4VxKAMykM06K/64z9rwiL6xnBHgB1+FVspqvlgCdwD1KUbQNfszeKVVOMh59w3sztHYIZDgnhOC4WbdEfHFDA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz", + "integrity": "sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", + "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz", + "integrity": "sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz", + "integrity": "sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", + "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.14.5.tgz", + "integrity": "sha512-CfmqxSUZzBl0rSjpoQSFoR9UEj3HzbGuGNL21/iFTmjb5gFggJp3ph0xR1YBhexmLoKRHzgxuFvty2xdSt6gTA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz", + "integrity": "sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz", + "integrity": "sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz", + "integrity": "sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz", + "integrity": "sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.5.tgz", + "integrity": "sha512-en8GfBtgnydoao2PS+87mKyw62k02k7kJ9ltbKe0fXTHrQmG6QZZflYuGI1VVG7sVpx4E1n7KBpNlPb8m78J+A==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.14.5.tgz", + "integrity": "sha512-mNMQdvBEE5DcMQaL5LbzXFMANrQjd2W7FPzg34Y4yEz7dBgdaC+9B84dSO+/1Wba98zoDbInctCDo4JGxz1VYA==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz", + "integrity": "sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz", + "integrity": "sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz", + "integrity": "sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz", + "integrity": "sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.5.tgz", + "integrity": "sha512-Tl7LWdr6HUxTmzQtzuU14SqbgrSKmaR77M0OKyq4njZLQTPfOvzblNKyNkGwOfEFCEx7KeYHQHDI0P3F02IVkA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz", + "integrity": "sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz", + "integrity": "sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz", + "integrity": "sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", + "integrity": "sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz", + "integrity": "sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz", + "integrity": "sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz", + "integrity": "sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz", + "integrity": "sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.14.5.tgz", + "integrity": "sha512-crTo4jATEOjxj7bt9lbYXcBAM3LZaUrbP2uUdxb6WIorLmjNKSpHfIybgY4B8SRpbf8tEVIWH3Vtm7ayCrKocA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz", + "integrity": "sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/preset-env": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.7.tgz", + "integrity": "sha512-itOGqCKLsSUl0Y+1nSfhbuuOlTs0MJk2Iv7iSH+XT/mR8U1zRLO7NjWlYXB47yhK4J/7j+HYty/EhFZDYKa/VA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.14.7", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.14.5", + "@babel/plugin-proposal-async-generator-functions": "^7.14.7", + "@babel/plugin-proposal-class-properties": "^7.14.5", + "@babel/plugin-proposal-class-static-block": "^7.14.5", + "@babel/plugin-proposal-dynamic-import": "^7.14.5", + "@babel/plugin-proposal-export-namespace-from": "^7.14.5", + "@babel/plugin-proposal-json-strings": "^7.14.5", + "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", + "@babel/plugin-proposal-numeric-separator": "^7.14.5", + "@babel/plugin-proposal-object-rest-spread": "^7.14.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.14.5", + "@babel/plugin-proposal-optional-chaining": "^7.14.5", + "@babel/plugin-proposal-private-methods": "^7.14.5", + "@babel/plugin-proposal-private-property-in-object": "^7.14.5", + "@babel/plugin-proposal-unicode-property-regex": "^7.14.5", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.14.5", + "@babel/plugin-transform-async-to-generator": "^7.14.5", + "@babel/plugin-transform-block-scoped-functions": "^7.14.5", + "@babel/plugin-transform-block-scoping": "^7.14.5", + "@babel/plugin-transform-classes": "^7.14.5", + "@babel/plugin-transform-computed-properties": "^7.14.5", + "@babel/plugin-transform-destructuring": "^7.14.7", + "@babel/plugin-transform-dotall-regex": "^7.14.5", + "@babel/plugin-transform-duplicate-keys": "^7.14.5", + "@babel/plugin-transform-exponentiation-operator": "^7.14.5", + "@babel/plugin-transform-for-of": "^7.14.5", + "@babel/plugin-transform-function-name": "^7.14.5", + "@babel/plugin-transform-literals": "^7.14.5", + "@babel/plugin-transform-member-expression-literals": "^7.14.5", + "@babel/plugin-transform-modules-amd": "^7.14.5", + "@babel/plugin-transform-modules-commonjs": "^7.14.5", + "@babel/plugin-transform-modules-systemjs": "^7.14.5", + "@babel/plugin-transform-modules-umd": "^7.14.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.14.7", + "@babel/plugin-transform-new-target": "^7.14.5", + "@babel/plugin-transform-object-super": "^7.14.5", + "@babel/plugin-transform-parameters": "^7.14.5", + "@babel/plugin-transform-property-literals": "^7.14.5", + "@babel/plugin-transform-regenerator": "^7.14.5", + "@babel/plugin-transform-reserved-words": "^7.14.5", + "@babel/plugin-transform-shorthand-properties": "^7.14.5", + "@babel/plugin-transform-spread": "^7.14.6", + "@babel/plugin-transform-sticky-regex": "^7.14.5", + "@babel/plugin-transform-template-literals": "^7.14.5", + "@babel/plugin-transform-typeof-symbol": "^7.14.5", + "@babel/plugin-transform-unicode-escapes": "^7.14.5", + "@babel/plugin-transform-unicode-regex": "^7.14.5", + "@babel/preset-modules": "^0.1.4", + "@babel/types": "^7.14.5", + "babel-plugin-polyfill-corejs2": "^0.2.2", + "babel-plugin-polyfill-corejs3": "^0.2.2", + "babel-plugin-polyfill-regenerator": "^0.2.2", + "core-js-compat": "^3.15.0", + "semver": "^6.3.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + } + } + }, + "@babel/runtime-corejs3": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.7.tgz", + "integrity": "sha512-Wvzcw4mBYbTagyBVZpAJWI06auSIj033T/yNE0Zn1xcup83MieCddZA7ls3kme17L4NOGBrQ09Q+nKB41RLWBA==", + "dev": true, + "requires": { + "core-js-pure": "^3.15.0", + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + } + } + }, + "@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/traverse": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", + "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.7", + "@babel/types": "^7.14.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + }, + "@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@gulp-sourcemaps/identity-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", + "integrity": "sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==", + "dev": true, + "requires": { + "acorn": "^6.4.1", + "normalize-path": "^3.0.0", + "postcss": "^7.0.16", + "source-map": "^0.6.0", + "through2": "^3.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } + } + }, + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "dev": true, + "requires": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/types": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.2.tgz", + "integrity": "sha512-XpjCtJ/99HB4PmyJ2vgmN7vT+JLP7RW1FBT9RgnMFS4Dt7cvIyBee8O3/j98aUZ34ZpenPZFqmaaObWSeL65dg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jsdevtools/coverage-istanbul-loader": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz", + "integrity": "sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==", + "dev": true, + "requires": { + "convert-source-map": "^1.7.0", + "istanbul-lib-instrument": "^4.0.3", + "loader-utils": "^2.0.0", + "merge-source-map": "^1.1.0", + "schema-utils": "^2.7.0" + } + }, + "@sindresorhus/is": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", + "dev": true, + "requires": { + "samsam": "1.3.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@types/aria-query": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz", + "integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==", + "dev": true + }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==", + "dev": true + }, + "@types/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==", + "dev": true + }, + "@types/cors": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", + "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==", + "dev": true + }, + "@types/easy-table": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/easy-table/-/easy-table-0.0.32.tgz", + "integrity": "sha512-zKh0f/ixYFnr3Ldf5ZJTi1ZpnRqAynTTtVyGvWDf/TT12asE8ac98t3/WGWfFdRPp/qsccxg82C/Kl3NPNhqEw==", + "dev": true + }, + "@types/ejs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.0.6.tgz", + "integrity": "sha512-fj1hi+ZSW0xPLrJJD+YNwIh9GZbyaIepG26E/gXvp8nCa2pYokxUYO1sK9qjGxp2g8ryZYuon7wmjpwE2cyASQ==", + "dev": true + }, + "@types/estree": { + "version": "0.0.48", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz", + "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==", + "dev": true, + "optional": true + }, + "@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", + "dev": true + }, + "@types/fibers": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/fibers/-/fibers-3.1.0.tgz", + "integrity": "sha512-1o3I9xtk2PZFxwaLCC6gTaBfBZ5rvw/DSZZPK89fwuwO6LNrzSbC6rEs1xI0bQ3fCRWmO+uNJQQeD2J56oTMDg==", + "dev": true + }, + "@types/fs-extra": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.11.tgz", + "integrity": "sha512-mZsifGG4QeQ7hlkhO56u7zt/ycBgGxSVsFI/6lGTU34VtwkiqrrSDgw0+ygs8kFGWcXnFQWMrzF2h7TtDFNixA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", + "dev": true + }, + "@types/inquirer": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.2.tgz", + "integrity": "sha512-EkeX/hU0SWinA2c7Qu/+6+7KbepFPYJcjankUgtA/VSY6BlVHybL0Cgyey9PDbXwhNXnNGBLU3t+MORp23RgAw==", + "dev": true, + "requires": { + "@types/through": "*", + "rxjs": "^6.4.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/lodash": { + "version": "4.14.170", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz", + "integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==", + "dev": true + }, + "@types/lodash.flattendeep": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@types/lodash.flattendeep/-/lodash.flattendeep-4.4.6.tgz", + "integrity": "sha512-uLm2MaRVlqJSGsMK0RZpP5T3KqReq+9WbYDHCUhBhp98v56hMG/Yht52bsoTSui9xz2mUvQ9NfG3LrNGDL92Ng==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.pickby": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/@types/lodash.pickby/-/lodash.pickby-4.6.6.tgz", + "integrity": "sha512-NFa13XxlMd9eFi0UFZFWIztpMpXhozbijrx3Yb1viYZphT7jyopIFVoIRF4eYMjruWNEG1rnyrRmg/8ej9T8Iw==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.union": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/@types/lodash.union/-/lodash.union-4.6.6.tgz", + "integrity": "sha512-Wu0ZEVNcyCz8eAn6TlUbYWZoGbH9E+iOHxAZbwUoCEXdUiy6qpcz5o44mMXViM4vlPLLCPlkAubEP1gokoSZaw==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/mdast": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz", + "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, + "@types/minimist": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", + "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", + "dev": true + }, + "@types/mocha": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.2.tgz", + "integrity": "sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==", + "dev": true + }, + "@types/node": { + "version": "15.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz", + "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==", + "dev": true + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "@types/puppeteer": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz", + "integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/recursive-readdir": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/recursive-readdir/-/recursive-readdir-2.2.0.tgz", + "integrity": "sha512-HGk753KRu2N4mWduovY4BLjYq4jTOL29gV2OfGdGxHcPSWGFkC5RRIdk+VTs5XmYd7MVAD+JwKrcb5+5Y7FOCg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/stack-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "dev": true + }, + "@types/stream-buffers": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.3.tgz", + "integrity": "sha512-NeFeX7YfFZDYsCfbuaOmFQ0OjSmHreKBpp7MQ4alWQBHeh2USLsj7qyMyn9t82kjqIX516CR/5SRHnARduRtbQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/through": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", + "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/unist": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", + "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", + "dev": true + }, + "@types/vinyl": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.4.tgz", + "integrity": "sha512-2o6a2ixaVI2EbwBPg1QYLGQoHK56p/8X/sGfKbFC8N6sY9lfjsMf/GprtkQkSya0D4uRiutRZ2BWj7k3JvLsAQ==", + "dev": true, + "requires": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, + "@types/which": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", + "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", + "dev": true + }, + "@types/yargs": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", + "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", + "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", + "dev": true + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "@vue/compiler-core": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.1.2.tgz", + "integrity": "sha512-nHmq7vLjq/XM2IMbZUcKWoH5sPXa2uR/nIKZtjbK5F3TcbnYE/zKsrSUR9WZJ03unlwotNBX1OyxVt9HbWD7/Q==", + "dev": true, + "optional": true, + "requires": { + "@babel/parser": "^7.12.0", + "@babel/types": "^7.12.0", + "@vue/shared": "3.1.2", + "estree-walker": "^2.0.1", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "@vue/compiler-dom": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.1.2.tgz", + "integrity": "sha512-k2+SWcWH0jL6WQAX7Or2ONqu5MbtTgTO0dJrvebQYzgqaKMXNI90RNeWeCxS4BnNFMDONpHBeFgbwbnDWIkmRg==", + "dev": true, + "optional": true, + "requires": { + "@vue/compiler-core": "3.1.2", + "@vue/shared": "3.1.2" + } + }, + "@vue/compiler-sfc": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.2.tgz", + "integrity": "sha512-SeG/2+DvwejQ7oAiSx8BrDh5qOdqCYHGClPiTvVIHTfSIHiS2JjMbCANdDCjHkTOh/O7WZzo2JhdKm98bRBxTw==", + "dev": true, + "optional": true, + "requires": { + "@babel/parser": "^7.13.9", + "@babel/types": "^7.13.0", + "@types/estree": "^0.0.48", + "@vue/compiler-core": "3.1.2", + "@vue/compiler-dom": "3.1.2", + "@vue/compiler-ssr": "3.1.2", + "@vue/shared": "3.1.2", + "consolidate": "^0.16.0", + "estree-walker": "^2.0.1", + "hash-sum": "^2.0.0", + "lru-cache": "^5.1.1", + "magic-string": "^0.25.7", + "merge-source-map": "^1.1.0", + "postcss": "^8.1.10", + "postcss-modules": "^4.0.0", + "postcss-selector-parser": "^6.0.4", + "source-map": "^0.6.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "optional": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "optional": true + } + } + }, + "@vue/compiler-ssr": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.2.tgz", + "integrity": "sha512-BwXo9LFk5OSWdMyZQ4bX1ELHX0Z/9F+ld/OaVnpUPzAZCHslBYLvyKUVDwv2C/lpLjRffpC2DOUEdl1+RP1aGg==", + "dev": true, + "optional": true, + "requires": { + "@vue/compiler-dom": "3.1.2", + "@vue/shared": "3.1.2" + } + }, + "@vue/shared": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.2.tgz", + "integrity": "sha512-EmH/poaDWBPJaPILXNI/1fvUbArJQmmTyVCwvvyDYDFnkPoTclAbHRAtyIvqfez7jybTDn077HTNILpxlsoWhg==", + "dev": true, + "optional": true + }, + "@wdio/browserstack-service": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-6.12.1.tgz", + "integrity": "sha512-B4zYlaE8q1Jxb6ctcuUPlKL3inwloETwks+cB9fFtVMDf/HH2Cau3Pi0CoIs8435EI+J4/1LxLHQV2uhzbBSlQ==", + "dev": true, + "requires": { + "@wdio/logger": "6.10.10", + "browserstack-local": "^1.4.5", + "got": "^11.0.2" + } + }, + "@wdio/cli": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.7.4.tgz", + "integrity": "sha512-npgpaIpPoSpyef1Pv0ZKyB5FJAZnDJiVphgda0RysXd6J0S3mlwzXrcIoapl28mmeWI6NIvvv65u0sominhsyQ==", + "dev": true, + "requires": { + "@types/ejs": "^3.0.5", + "@types/fs-extra": "^9.0.4", + "@types/inquirer": "^7.3.1", + "@types/lodash.flattendeep": "^4.4.6", + "@types/lodash.pickby": "^4.6.6", + "@types/lodash.union": "^4.6.6", + "@types/recursive-readdir": "^2.2.0", + "@wdio/config": "7.7.3", + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", + "async-exit-hook": "^2.0.1", + "chalk": "^4.0.0", + "chokidar": "^3.0.0", + "cli-spinners": "^2.1.0", + "ejs": "^3.0.1", + "fs-extra": "^10.0.0", + "inquirer": "^8.0.0", + "lodash.flattendeep": "^4.4.0", + "lodash.pickby": "^4.6.0", + "lodash.union": "^4.6.0", + "mkdirp": "^1.0.4", + "recursive-readdir": "^2.2.2", + "webdriverio": "7.7.4", + "yargs": "^17.0.0", + "yarn-install": "^1.0.0" + }, + "dependencies": { + "@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz", + "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + } + } + }, + "@wdio/concise-reporter": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-7.7.3.tgz", + "integrity": "sha512-2Ix20n48N+lvvU4NzqMP7z+daG748RRsmDqdstCoBrJgXV6frvu38HVHV90U5uKt5Vmp6/QQl05A4OliaNoO9w==", + "dev": true, + "requires": { + "@wdio/reporter": "7.7.3", + "@wdio/types": "7.7.3", + "chalk": "^4.0.0", + "pretty-ms": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@wdio/config": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.7.3.tgz", + "integrity": "sha512-I8gkb5BjXLe6/9NK7OCA9Mc+A6xeGUqbYTRd4PNKdObE6HomKOxw4plVZCYF0DlD2FCo4OGrvYGmalojFsCMdA==", + "dev": true, + "requires": { + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + }, + "dependencies": { + "@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@wdio/local-runner": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.7.4.tgz", + "integrity": "sha512-ubBr9+pDZuOg6i/EJdW8E71dXrE1A63+wsZH6lpdm1fFWqfRvjl+DTCdE2rtLhr44vNSmiHxIIQnCjvZXwjiFg==", + "dev": true, + "requires": { + "@types/stream-buffers": "^3.0.3", + "@wdio/logger": "7.7.0", + "@wdio/repl": "7.7.3", + "@wdio/runner": "7.7.4", + "@wdio/types": "7.7.3", + "async-exit-hook": "^2.0.1", + "split2": "^3.2.2", + "stream-buffers": "^3.0.2" + }, + "dependencies": { + "@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@wdio/logger": { + "version": "6.10.10", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.10.10.tgz", + "integrity": "sha512-2nh0hJz9HeZE0VIEMI+oPgjr/Q37ohrR9iqsl7f7GW5ik+PnKYCT9Eab5mR1GNMG60askwbskgGC1S9ygtvrSw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@wdio/mocha-framework": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.7.4.tgz", + "integrity": "sha512-zLhMJBAp4HOP0qGffCNSA1UBdRystn9o5y7EEQXU6Gu+ktrSOV/RU+pvd+kqHo6RfOIcwShljZVStf3zh8cY6Q==", + "dev": true, + "requires": { + "@types/mocha": "^8.0.0", + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", + "expect-webdriverio": "^3.0.0", + "mocha": "^9.0.0" + }, + "dependencies": { + "@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "mocha": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.1.tgz", + "integrity": "sha512-9zwsavlRO+5csZu6iRtl3GHImAbhERoDsZwdRkdJ/bE+eVplmoxNKE901ZJ9LdSchYBjSCPbjKc5XvcAri2ylw==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.7", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.23", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.4", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + } + } + }, + "@wdio/protocols": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.7.4.tgz", + "integrity": "sha512-gfGPOjvqUws3/dTnrXbCYP2keYE6O5BK5qHWnOEu6c7ubE4hebxV8W5c822L7ntabc1e38+diEbM+qFuIT890Q==", + "dev": true + }, + "@wdio/repl": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.7.3.tgz", + "integrity": "sha512-7nhvUa3Zd5Ny9topJGRZwkomlveuO3RIv+jBUHgQ2jiDIGvG9MroHxKEniIbscVSsD32XFOOZY59kSpX1b50VQ==", + "dev": true, + "requires": { + "@wdio/utils": "7.7.3" + } + }, + "@wdio/reporter": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.7.3.tgz", + "integrity": "sha512-zAUGgP/FZ3XF5s4RUcDGIAeum3WzkA9ll5lymytxhh/9Jj9/5c77o498ic3RGQlB8FTz+5SVmw08r7g3uekI8g==", + "dev": true, + "requires": { + "@types/node": "^14.14.31", + "@wdio/types": "7.7.3", + "fs-extra": "^10.0.0" + }, + "dependencies": { + "@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true + } + } + }, + "@wdio/runner": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.7.4.tgz", + "integrity": "sha512-Ahfrv3TM9y2KMjWI1xKc+tnLVO+X1/Gf5QPjprmLlRxf/rSQDfX+wMmQP/g0wsLtm4pXy0kR1K/76WWvZgzSkw==", + "dev": true, + "requires": { + "@wdio/config": "7.7.3", + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", + "deepmerge": "^4.0.0", + "gaze": "^1.1.2", + "webdriver": "7.7.4", + "webdriverio": "7.7.4" + }, + "dependencies": { + "@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@wdio/spec-reporter": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.7.3.tgz", + "integrity": "sha512-5elsNfZd3kbBaKY5IK5ZmdZsWZNSOCqXnM2fYryAh2RBoXbcXkak4D5PbLehusZhp6CQ7UpXEKf4BDDYfd0ebw==", + "dev": true, + "requires": { + "@types/easy-table": "^0.0.32", + "@wdio/reporter": "7.7.3", + "@wdio/types": "7.7.3", + "chalk": "^4.0.0", + "easy-table": "^1.1.1", + "pretty-ms": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@wdio/sync": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-7.7.4.tgz", + "integrity": "sha512-x0ZU78Je0yl05TfwiNtkKkJZ+90y6MndR4z5n/m6ADRzSGdFOazGJSFO0h2bN8MkPRusfqYsJwB6MKftCP0URA==", + "dev": true, + "requires": { + "@types/fibers": "^3.1.0", + "@types/puppeteer": "^5.4.0", + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "fibers": "^5.0.0", + "webdriverio": "7.7.4" + }, + "dependencies": { + "@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@wdio/types": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.7.3.tgz", + "integrity": "sha512-ZZBQHCXKjZSQj9pf4df/QhfgQQj0vzm9hkK7YyNM+S+qnW0LExL8qQKLxTlGHDaYxk/+Jrd9pcZrJXRCoSnUaA==", + "dev": true, + "requires": { + "@types/node": "^14.14.31", + "got": "^11.8.1" + }, + "dependencies": { + "@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true + } + } + }, + "@wdio/utils": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.7.3.tgz", + "integrity": "sha512-bvOoE2gve8Z8HFguVw0RMp5BbSmJR4zSr8DwbwnA8RSL3NshKlRk33HWYLmKsxjkH+ZWI2ihFbpvLD4W4imXag==", + "dev": true, + "requires": { + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3" + }, + "dependencies": { + "@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "dev": true, + "requires": { + "acorn": "^4.0.3" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + } + } + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true, + "requires": {} + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "add-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", + "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=", + "dev": true + }, + "adm-zip": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.2.1.tgz", + "integrity": "sha1-6AHO3rW9mk6Y1pnFwPQjnicx3L8=", + "dev": true + }, + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + }, + "dependencies": { + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + } + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "^1.0.0" + } + }, + "archiver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", + "dev": true, + "requires": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "dependencies": { + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true + } + } + }, + "archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "requires": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", + "dev": true + }, + "array-includes": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", + "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.5" + } + }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "requires": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "requires": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "array.prototype.flat": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", + "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "astw": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", + "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=", + "dev": true, + "requires": { + "acorn": "^4.0.3" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + } + } + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "requires": { + "async-done": "^1.2.2" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "available-typed-arrays": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz", + "integrity": "sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-loader": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", + "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^1.4.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", + "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.2", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.3.tgz", + "integrity": "sha512-rCOFzEIJpJEAU14XCcV/erIf/wZQMmMT5l5vXOpL5uoznyOGfDIjPj6FVytMvtzaKSTSVKouOCTPJ5OMUZH30g==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.2", + "core-js-compat": "^3.14.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", + "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.2" + } + }, + "babel-plugin-transform-object-assign": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.22.0.tgz", + "integrity": "sha1-+Z0vZvGgsNSY40bFNZaEdAyqILo=", + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + } + } + }, + "babelify": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz", + "integrity": "sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==", + "dev": true, + "requires": {} + }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "requires": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + } + }, + "bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "Base64": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", + "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg=", + "dev": true + }, + "base64-arraybuffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "beefy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/beefy/-/beefy-1.1.0.tgz", + "integrity": "sha1-SOAy/G5nJpo8jUILABdEc7jbnyA=", + "dev": true, + "requires": { + "chokidar": "0.8.1", + "colors": "~0.6.0-1", + "ignorepatterns": "1.0.1", + "mime": "~1.2.9", + "nopt": "~2.1.1", + "open": "0.0.3", + "portfinder": "~0.2.1", + "response-stream": "0.0.0", + "script-injector": "~0.1.0", + "sse-stream": "0.0.4", + "through": "~2.2.0" + }, + "dependencies": { + "chokidar": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-0.8.1.tgz", + "integrity": "sha1-juDJnvSEIJAv3tc7hi6sLdddpKY=", + "dev": true + }, + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true + }, + "nopt": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", + "integrity": "sha1-bMzZd7gBMqB3MdbozljCyDA8+a8=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "through": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/through/-/through-2.2.7.tgz", + "integrity": "sha1-bo4hIAGR1OtqmfbwEN9Gqhxusr0=", + "dev": true + } + } + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true + }, + "bfj": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", + "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "check-types": "^8.0.3", + "hoopy": "^0.1.4", + "tryer": "^1.0.1" + } + }, + "big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "dev": true, + "requires": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "binaryextensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", + "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + } + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", + "dev": true + }, + "body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", + "dev": true, + "requires": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + }, + "dependencies": { + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", + "dev": true + }, + "raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", + "dev": true, + "requires": { + "bytes": "1", + "string_decoder": "0.10" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", + "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", + "dev": true, + "optional": true, + "requires": { + "hoek": "0.9.x" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "brfs": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/brfs/-/brfs-0.0.9.tgz", + "integrity": "sha1-kkVbw2+74SZ6N5Z736lXEIgZaro=", + "dev": true, + "requires": { + "escodegen": "0.0.17", + "falafel": "~0.1.6", + "through": "~2.2.0" + }, + "dependencies": { + "escodegen": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-0.0.17.tgz", + "integrity": "sha1-HnjRffEAT9eojy/tO4uFkvMhf5w=", + "dev": true, + "requires": { + "esprima": "~1.0.2", + "estraverse": "~0.0.4", + "source-map": ">= 0.1.2" + } + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "estraverse": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-0.0.4.tgz", + "integrity": "sha1-AaCTLf7ldGhKWYr1pnw7+bZCjbI=", + "dev": true + }, + "through": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/through/-/through-2.2.7.tgz", + "integrity": "sha1-bo4hIAGR1OtqmfbwEN9Gqhxusr0=", + "dev": true + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-pack": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-3.2.0.tgz", + "integrity": "sha1-+qHLxBSHsazEdH43PhFIrf/Q4tk=", + "dev": true, + "requires": { + "combine-source-map": "~0.3.0", + "concat-stream": "~1.4.1", + "defined": "~0.0.0", + "JSONStream": "~0.8.4", + "through2": "~0.5.1", + "umd": "^2.1.0" + }, + "dependencies": { + "concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "defined": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true + }, + "JSONStream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", + "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=", + "dev": true, + "requires": { + "jsonparse": "0.0.5", + "through": ">=2.2.7 <3" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + } + } + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserify": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-8.1.3.tgz", + "integrity": "sha1-8zpUmjpsNoIZsHX9z+bfGV4Empo=", + "dev": true, + "requires": { + "assert": "~1.3.0", + "browser-pack": "^3.2.0", + "browser-resolve": "^1.3.0", + "browserify-zlib": "~0.1.2", + "buffer": "^3.0.0", + "builtins": "~0.0.3", + "commondir": "0.0.1", + "concat-stream": "~1.4.1", + "console-browserify": "^1.1.0", + "constants-browserify": "~0.0.1", + "crypto-browserify": "^3.0.0", + "deep-equal": "~0.2.1", + "defined": "~0.0.0", + "deps-sort": "^1.3.5", + "domain-browser": "~1.1.0", + "duplexer2": "~0.0.2", + "events": "~1.0.0", + "glob": "^4.0.5", + "http-browserify": "^1.4.0", + "https-browserify": "~0.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^6.2.0", + "isarray": "0.0.1", + "JSONStream": "~0.8.3", + "labeled-stream-splicer": "^1.0.0", + "module-deps": "^3.6.3", + "os-browserify": "~0.1.1", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "^0.10.0", + "punycode": "~1.2.3", + "querystring-es3": "~0.2.0", + "readable-stream": "^1.0.33-1", + "resolve": "~0.7.1", + "shallow-copy": "0.0.1", + "shasum": "^1.0.0", + "shell-quote": "~0.0.1", + "stream-browserify": "^1.0.0", + "string_decoder": "~0.10.0", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^1.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "~0.0.0", + "umd": "~2.1.0", + "url": "~0.10.1", + "util": "~0.10.1", + "vm-browserify": "~0.0.1", + "xtend": "^3.0.0" + }, + "dependencies": { + "assert": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.3.0.tgz", + "integrity": "sha1-A5OaYiWCqBLMICMgoLmlbJuBWEk=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=", + "dev": true + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "requires": { + "pako": "~0.2.0" + } + }, + "buffer": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.2.tgz", + "integrity": "sha512-c3M77NkHJxS0zx/ErxXhDLr1v3y2MDXPeTJPvLNOaIYJ4ymHBUFQ9EXzt9HYuqAJllMoNb/EZ8hIiulnQFAUuQ==", + "dev": true, + "requires": { + "base64-js": "0.0.8", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "commondir": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-0.0.1.tgz", + "integrity": "sha1-ifAP3NUbUZxXhzP+xWPmptp/W+I=", + "dev": true + }, + "concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "constants-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-0.0.1.tgz", + "integrity": "sha1-kld9tSe6bEzwpFaNhLwDH0QeIfI=", + "dev": true + }, + "deep-equal": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", + "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=", + "dev": true + }, + "defined": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", + "dev": true + }, + "domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9" + } + }, + "events": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/events/-/events-1.0.2.tgz", + "integrity": "sha1-dYSdz+k9EPsFfDAFWv29UdBqjiQ=", + "dev": true + }, + "glob": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^2.0.1", + "once": "^1.3.0" + } + }, + "https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true + }, + "JSONStream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", + "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=", + "dev": true, + "requires": { + "jsonparse": "0.0.5", + "through": ">=2.2.7 <3" + } + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + }, + "os-browserify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", + "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=", + "dev": true + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "process": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/process/-/process-0.10.1.tgz", + "integrity": "sha1-hCRXzFHP7XLcd1r+6vuMYDQ3JyU=", + "dev": true + }, + "punycode": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.2.4.tgz", + "integrity": "sha1-VACKyXKux0F13vnLpt9/qdORh0A=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "resolve": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.7.4.tgz", + "integrity": "sha1-OVqe+ehz+/4SvRRAi9kbuTYAPWk=", + "dev": true + }, + "stream-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-1.0.0.tgz", + "integrity": "sha1-v5tKv7QrJ011FHnkTg/yZWtvEZM=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^1.0.27-1" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "requires": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + }, + "dependencies": { + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "requires": { + "process": "~0.11.0" + }, + "dependencies": { + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + } + } + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + } + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + } + }, + "browserstack": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", + "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + }, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "browserstack-local": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.4.8.tgz", + "integrity": "sha512-s+mc3gTOJwELdLWi4qFVKtGwMbb5JWsR+JxKlMaJkRJxoZ0gg3WREgPxAN0bm6iU5+S4Bi0sz0oxBRZT8BiNsQ==", + "dev": true, + "requires": { + "https-proxy-agent": "^4.0.0", + "is-running": "^2.1.0", + "ps-tree": "=1.2.0", + "temp-fs": "^0.9.9" + } + }, + "browserstacktunnel-wrapper": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.4.tgz", + "integrity": "sha512-GCV599FUUxNOCFl3WgPnfc5dcqq9XTmMXoxWpqkvmk0R9TOIoqmjENNU6LY6DtgIL6WfBVbg/jmWtnM5K6UYSg==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1", + "unzipper": "^0.9.3" + }, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "dev": true + }, + "buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "builtins": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-0.0.7.tgz", + "integrity": "sha1-NVIZzWzxjb58Acx/0tznZc/cVJo=", + "dev": true + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "cac": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/cac/-/cac-3.0.4.tgz", + "integrity": "sha1-bSTO7Dcu/lybeYgIvH9JtHJCpO8=", + "dev": true, + "requires": { + "camelcase-keys": "^3.0.0", + "chalk": "^1.1.3", + "indent-string": "^3.0.0", + "minimist": "^1.2.0", + "read-pkg-up": "^1.0.1", + "suffix": "^0.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true + }, + "cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + } + }, + "cached-path-relative": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz", + "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + }, + "dependencies": { + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + } + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "camelcase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-3.0.0.tgz", + "integrity": "sha1-/AxsNgNj9zd+N5O5oWvM8QcMHKQ=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "map-obj": "^1.0.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001239", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001239.tgz", + "integrity": "sha512-cyBkXJDMeI4wthy8xJ2FvDU6+0dtcZSJW3voUF8+e9f1bBeuvyZfc3PNbkOETyhbR+dGCPzn9E7MA3iwzusOhQ==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "ccount": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", + "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "dev": true, + "requires": { + "traverse": ">=0.3.0 <0.4" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true + }, + "character-entities-html4": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", + "dev": true + }, + "character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true + }, + "character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "check-types": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", + "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", + "dev": true + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "chrome-launcher": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.14.0.tgz", + "integrity": "sha512-W//HpflaW6qBGrmuskup7g+XJZN6w03ko9QSIe5CtcTal2u0up5SeReK3Ll1Why4Ey8dPkv8XSodZyHPnGbVHQ==", + "dev": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + } + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } + "is-descriptor": "^0.1.0" } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + } + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", + "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==", + "dev": true + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "safe-buffer": "~5.1.0" } + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "combine-source-map": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.3.0.tgz", + "integrity": "sha1-2edPWT2c1DgHMSy12EbUUe+qnrc=", + "dev": true, + "requires": { + "convert-source-map": "~0.3.0", + "inline-source-map": "~0.3.0", + "source-map": "~0.1.31" + }, + "dependencies": { + "convert-source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", + "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=", + "dev": true }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", "dev": true, - "optional": true, "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" + "amdefine": ">=0.0.4" } - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", - "dev": true - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + } + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "compress-commons": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "dev": true, + "requires": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "safe-buffer": "~5.1.0" } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + } + } + }, + "concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + } + } + }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + } + }, + "connect-livereload": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", + "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", + "dev": true + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "consolidate": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.16.0.tgz", + "integrity": "sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==", + "dev": true, + "optional": true, + "requires": { + "bluebird": "^3.7.2" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", + "dev": true + }, + "conventional-changelog": { + "version": "3.1.24", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.24.tgz", + "integrity": "sha512-ed6k8PO00UVvhExYohroVPXcOJ/K1N0/drJHx/faTH37OIZthlecuLIRX/T6uOp682CAoVoFpu+sSEaeuH6Asg==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.12", + "conventional-changelog-atom": "^2.0.8", + "conventional-changelog-codemirror": "^2.0.8", + "conventional-changelog-conventionalcommits": "^4.5.0", + "conventional-changelog-core": "^4.2.1", + "conventional-changelog-ember": "^2.0.9", + "conventional-changelog-eslint": "^3.0.9", + "conventional-changelog-express": "^2.0.6", + "conventional-changelog-jquery": "^3.0.11", + "conventional-changelog-jshint": "^2.0.9", + "conventional-changelog-preset-loader": "^2.3.4" + } + }, + "conventional-changelog-angular": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz", + "integrity": "sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + } + }, + "conventional-changelog-atom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", + "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", + "dev": true, + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-codemirror": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", + "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", + "dev": true, + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-config-spec": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz", + "integrity": "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==", + "dev": true + }, + "conventional-changelog-conventionalcommits": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.5.0.tgz", + "integrity": "sha512-buge9xDvjjOxJlyxUnar/+6i/aVEVGA7EEh4OafBCXPlLUQPGbRUBhBUveWRxzvR8TEjhKEP4BdepnpG2FSZXw==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "lodash": "^4.17.15", + "q": "^1.5.1" + } + }, + "conventional-changelog-core": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.2.tgz", + "integrity": "sha512-7pDpRUiobQDNkwHyJG7k9f6maPo9tfPzkSWbRq97GGiZqisElhnvUZSvyQH20ogfOjntB5aadvv6NNcKL1sReg==", + "dev": true, + "requires": { + "add-stream": "^1.0.0", + "conventional-changelog-writer": "^4.0.18", + "conventional-commits-parser": "^3.2.0", + "dateformat": "^3.0.0", + "get-pkg-repo": "^1.0.0", + "git-raw-commits": "^2.0.8", + "git-remote-origin-url": "^2.0.0", + "git-semver-tags": "^4.1.1", + "lodash": "^4.17.15", + "normalize-package-data": "^3.0.0", + "q": "^1.5.1", + "read-pkg": "^3.0.0", + "read-pkg-up": "^3.0.0", + "shelljs": "^0.8.3", + "through2": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "locate-path": "^2.0.0" } }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "hosted-git-info": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "lru-cache": "^6.0.0" } }, "load-json-file": { @@ -7105,25 +38351,25 @@ "path-exists": "^3.0.0" } }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "yallist": "^4.0.0" + } + }, + "normalize-package-data": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", + "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" } }, "p-limit": { @@ -7190,6 +38436,32 @@ "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", "path-type": "^3.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "read-pkg-up": { @@ -7202,78 +38474,13 @@ "read-pkg": "^3.0.0" } }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "lru-cache": "^6.0.0" } }, "strip-bom": { @@ -7282,1117 +38489,1375 @@ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true - }, - "yargs": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", - "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", - "dev": true, - "requires": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" - }, - "dependencies": { - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - } - } - }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } } } }, - "dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "conventional-changelog-ember": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", + "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", + "dev": true, + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-eslint": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", + "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", + "dev": true, + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-express": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", + "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", + "dev": true, + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-jquery": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", + "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", "dev": true, "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" + "q": "^1.5.1" } }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "conventional-changelog-jshint": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", + "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", "dev": true, "requires": { - "webidl-conversions": "^4.0.2" + "compare-func": "^2.0.0", + "q": "^1.5.1" } }, - "dset": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/dset/-/dset-2.0.1.tgz", - "integrity": "sha512-nI29OZMRYq36hOcifB6HTjajNAAiBKSXsyWZrq+VniusseuP2OpNlTiYgsaNRSGvpyq5Wjbc2gQLyBdTyWqhnQ==" - }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "conventional-changelog-preset-loader": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", + "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", "dev": true }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "conventional-changelog-writer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz", + "integrity": "sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw==", "dev": true, "requires": { - "readable-stream": "^2.0.2" + "compare-func": "^2.0.0", + "conventional-commits-filter": "^2.0.7", + "dateformat": "^3.0.0", + "handlebars": "^4.7.6", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^4.0.0" }, "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "through": "2" } } } }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true + "conventional-commits-filter": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", + "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "dev": true, + "requires": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + } }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "conventional-commits-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.1.tgz", + "integrity": "sha512-OG9kQtmMZBJD/32NEw5IhN5+HnBqVjy03eC+I71I0oQRFA5rOgA4OtPOYG7mz1GkCfCNxn3gKIX8EiHJYuf1cA==", "dev": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" + "is-text-path": "^1.0.1", + "JSONStream": "^1.0.4", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0", + "trim-off-newlines": "^1.0.0" + } + }, + "conventional-recommended-bump": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz", + "integrity": "sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==", + "dev": true, + "requires": { + "concat-stream": "^2.0.0", + "conventional-changelog-preset-loader": "^2.3.4", + "conventional-commits-filter": "^2.0.7", + "conventional-commits-parser": "^3.2.0", + "git-raw-commits": "^2.0.8", + "git-semver-tags": "^4.1.1", + "meow": "^8.0.0", + "q": "^1.5.1" }, "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" } } } }, - "each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, "requires": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" + "safe-buffer": "~5.1.1" } }, - "easy-table": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.1.tgz", - "integrity": "sha512-C9Lvm0WFcn2RgxbMnTbXZenMIWcBtkzMr+dWqq/JsVoGFSVUVlPqeOa5LP5kM0I3zoOazFpckOEb2/0LDFfToQ==", + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-props": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", "dev": true, "requires": { - "ansi-regex": "^3.0.0", - "wcwidth": ">=1.0.1" + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true } } }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "core-js": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.15.1.tgz", + "integrity": "sha512-h8VbZYnc9pDzueiS2610IULDkpFFPunHwIpl8yRwFahAEEdSpHlTy3h3z3rKq5h11CaUdBEeRViu9AYvbxiMeg==" + }, + "core-js-compat": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.1.tgz", + "integrity": "sha512-xGhzYMX6y7oEGQGAJmP2TmtBLvR4nZmRGEcFa3ubHOq5YEp51gGN9AovVa0AoujGZIq+Wm6dISiYyGNfdflYww==", "dev": true, "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "browserslist": "^4.16.6", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } } }, - "editions": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz", - "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==", - "dev": true + "core-js-pure": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.15.1.tgz", + "integrity": "sha512-OZuWHDlYcIda8sJLY4Ec6nWq2hRjlyCqCZ+jCflyleMkVt3tPedDVErvHslyS2nbO+SlBFMSBJYvtLMwxnrzjA==" }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, - "ejs": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.3.tgz", - "integrity": "sha512-wmtrUGyfSC23GC/B1SMv2ogAUgbQEtDmTIhfqielrG5ExIM9TP4UoYdi90jLF1aTcsWCJNEO0UrgKzP0y3nTSg==", + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dev": true, "requires": { - "jake": "^10.6.1" + "object-assign": "^4", + "vary": "^1" } }, - "electron-to-chromium": { - "version": "1.3.505", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.505.tgz", - "integrity": "sha512-Aunrp3HWtmdiJLIl+IPSFtEvJ/4Q9a3eKaxmzCthaZF1gbTbpHUTCU2zOVnFPH7r/AD7zQXyuFidYXzSHXBdsw==", - "dev": true - }, - "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "coveralls": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.0.tgz", + "integrity": "sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.2" }, "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } } } }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "dev": true, + "requires": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + } }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", "dev": true, "requires": { - "once": "^1.4.0" + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" } }, - "engine.io": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", - "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "dev": true, "requires": { - "accepts": "~1.3.4", - "base64id": "1.0.0", - "cookie": "0.3.1", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.0", - "ws": "~3.3.1" + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" }, "dependencies": { - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "criteo-direct-rsa-validate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/criteo-direct-rsa-validate/-/criteo-direct-rsa-validate-1.1.0.tgz", + "integrity": "sha512-7gQ3zX+d+hS/vOxzLrZ4aRAceB7qNJ0VzaGNpcWjDCmtOpASB50USJDupTik/H2nHgiSAA3VNZ3SFuONs8LR9Q==" + }, + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "isexe": "^2.0.0" } } } }, - "engine.io-client": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", - "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", + "cryptiles": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", + "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", "dev": true, + "optional": true, "requires": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.1", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "~3.3.1", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" + "boom": "0.4.x" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "crypto-js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + }, + "css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" }, "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", "dev": true, "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" } } } }, - "engine.io-parser": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", - "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "css-shorthand-properties": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", + "integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==", + "dev": true + }, + "css-value": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", + "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=", + "dev": true + }, + "cssauron": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", + "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", "dev": true, "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.5", - "has-binary2": "~1.0.2" + "through": "X.X.X" } }, - "enhanced-resolve": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", - "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "optional": true + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", + "dev": true, + "optional": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.4.0", - "object-assign": "^4.0.1", - "tapable": "^0.2.7" + "array-find-index": "^1.0.1" } }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", "dev": true }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", "dev": true, "requires": { - "prr": "~1.0.1" + "es5-ext": "^0.10.50", + "type": "^1.0.1" } }, - "error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", + "dargs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { - "string-template": "~0.2.1" + "assert-plus": "^1.0.0" } }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "date-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", + "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", + "dev": true + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "is-arrayish": "^0.2.1" + "ms": "2.0.0" } }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", "dev": true, "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" }, "dependencies": { - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "has-symbols": "^1.0.1" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true } } }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", "dev": true }, - "es-get-iterator": { + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "decamelize-keys": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", "dev": true, "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" }, "dependencies": { - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true } } }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "es5-shim": { - "version": "4.5.14", - "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.5.14.tgz", - "integrity": "sha512-7SwlpL+2JpymWTt8sNLuC2zdhhc+wrfe5cMPI2j0o6WsPdfAiPwmFy2f0AocPB4RQVBOZ9kNTgi5YF7TdhkvEg==", - "dev": true - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" + "mimic-response": "^3.1.0" }, "dependencies": { - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true } } }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" + "type-detect": "^4.0.0" } }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "deep-equal": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", + "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" + "call-bind": "^1.0.0", + "es-get-iterator": "^1.1.1", + "get-intrinsic": "^1.0.1", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.1.1", + "isarray": "^2.0.5", + "object-is": "^1.1.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.3", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } } }, - "escalade": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", - "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", "dev": true, "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" + "kind-of": "^5.0.2" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true } } }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "dev": true, "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "clone": "^1.0.2" } }, - "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", - "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", - "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^1.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" }, "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "deps-sort": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-1.3.9.tgz", + "integrity": "sha1-Kd//U+F7Nq7K51MK27v2IsLtGnE=", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "shasum": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", "dev": true, "requires": { - "ms": "^2.1.1" + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" } + } + } + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "detective": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", + "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "dev": true, + "requires": { + "acorn-node": "^1.6.1", + "defined": "^1.0.0", + "minimist": "^1.1.1" + } + }, + "devtools": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.7.4.tgz", + "integrity": "sha512-rkO9k6yOA2XzFTph9y+gO/387653jou0La7QSLd57XTQiM3D/UODqLBt+fMVu8w3fdQzZHVAlIIvP4B8rkXY1Q==", + "dev": true, + "requires": { + "@types/node": "^14.14.31", + "@wdio/config": "7.7.3", + "@wdio/logger": "7.7.0", + "@wdio/protocols": "7.7.4", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", + "chrome-launcher": "^0.14.0", + "edge-paths": "^2.1.0", + "puppeteer-core": "^9.1.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^0.7.21", + "uuid": "^8.0.0" + }, + "dependencies": { + "@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", "dev": true, "requires": { - "esutils": "^2.0.2" + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" } }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" + "color-convert": "^2.0.1" } }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.4", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" + "color-name": "~1.1.4" } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "has-flag": "^4.0.0" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + } + } + }, + "devtools-protocol": { + "version": "0.0.892017", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.892017.tgz", + "integrity": "sha512-23yn1+zeMBlWiZTtrCViNQt+W+FRDw5rEetI19bMuyKIYeK11xo/dS+Hmuu8ifGJnJvXUU3Y79IoxSPWZWcVOA==", + "dev": true + }, + "dezalgo": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", + "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "diff-sequences": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.1.tgz", + "integrity": "sha512-XPLijkfJUh/PIBnfkcSHgvD6tlYixmcMAn3osTk6jt+H0v/mgURto1XUiD9DKuGX5NDoVS6dSlA23gd9FUaCFg==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + } + } + }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "doctrine-temporary-fork": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz", + "integrity": "sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "documentation": { + "version": "13.2.5", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-13.2.5.tgz", + "integrity": "sha512-d1TrfrHXYZR63xrOzkYwwe297vkSwBoEhyyMBOi20T+7Ohe1aX1dW4nqXncQmdmE5MxluSaxxa3BW1dCvbF5AQ==", + "dev": true, + "requires": { + "@babel/core": "7.12.3", + "@babel/generator": "7.12.1", + "@babel/parser": "7.12.3", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "@vue/compiler-sfc": "^3.0.11", + "ansi-html": "^0.0.7", + "babelify": "^10.0.0", + "chalk": "^2.3.0", + "chokidar": "^3.4.0", + "concat-stream": "^1.6.0", + "diff": "^4.0.1", + "doctrine-temporary-fork": "2.1.0", + "get-port": "^5.0.0", + "git-url-parse": "^11.1.2", + "github-slugger": "1.2.0", + "glob": "^7.1.2", + "globals-docs": "^2.4.0", + "highlight.js": "^10.7.2", + "ini": "^1.3.5", + "js-yaml": "^3.10.0", + "lodash": "^4.17.10", + "mdast-util-find-and-replace": "^1.1.1", + "mdast-util-inject": "^1.1.0", + "micromatch": "^3.1.5", + "mime": "^2.2.0", + "module-deps-sortable": "^5.0.3", + "parse-filepath": "^1.0.2", + "pify": "^5.0.0", + "read-pkg-up": "^4.0.0", + "remark": "^13.0.0", + "remark-gfm": "^1.0.0", + "remark-html": "^13.0.1", + "remark-reference-links": "^5.0.0", + "remark-toc": "^7.2.0", + "resolve": "^1.8.1", + "stream-array": "^1.1.2", + "strip-json-comments": "^2.0.1", + "tiny-lr": "^1.1.0", + "unist-builder": "^2.0.3", + "unist-util-visit": "^2.0.3", + "vfile": "^4.0.0", + "vfile-reporter": "^6.0.0", + "vfile-sort": "^2.1.0", + "vinyl": "^2.1.0", + "vinyl-fs": "^3.0.2", + "vue-template-compiler": "^2.6.12", + "yargs": "^15.3.1" + }, + "dependencies": { + "@babel/core": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", + "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.1", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.1", + "@babel/parser": "^7.12.3", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" } }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "@babel/generator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", + "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", "dev": true, "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" + "@babel/types": "^7.12.1", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" } }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "@babel/parser": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", + "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "color-convert": "^2.0.1" } }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "sprintf-js": "~1.0.2" } }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } - } - }, - "eslint-config-standard": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", - "integrity": "sha1-wGHk0GbzedwXzVYsZOgZtN1FRZE=", - "dev": true - }, - "eslint-import-resolver-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", - "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "resolve": "^1.13.1" - } - }, - "eslint-module-utils": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", - "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "p-try": "^1.0.0" + "color-name": "~1.1.4" } }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "ms": "2.1.2" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "find-up": "^2.1.0" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } - } - } - }, - "eslint-plugin-import": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz", - "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==", - "dev": true, - "requires": { - "array-includes": "^3.1.1", - "array.prototype.flat": "^1.2.3", - "contains-path": "^0.1.0", - "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.3", - "eslint-module-utils": "^2.6.0", - "has": "^1.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.1", - "read-pkg-up": "^2.0.0", - "resolve": "^1.17.0", - "tsconfig-paths": "^3.9.0" - }, - "dependencies": { + }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "^3.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", + "parse-json": "^4.0.0", + "pify": "^3.0.0", "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-limit": "^2.0.0" } }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "p-limit": "^1.1.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -8400,580 +39865,469 @@ "dev": true }, "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "pify": "^2.0.0" + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, + "pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "dev": true + }, "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "load-json-file": "^2.0.0", + "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" + "path-type": "^3.0.0" } }, "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" } }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true - } - } - }, - "eslint-plugin-node": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-5.2.1.tgz", - "integrity": "sha512-xhPXrh0Vl/b7870uEbaumb2Q+LxaEcOQ3kS1jtIXanBAwpMre1l5q/l2l/hESYJGEFKuI78bp6Uw50hlpr7B+g==", - "dev": true, - "requires": { - "ignore": "^3.3.6", - "minimatch": "^3.0.4", - "resolve": "^1.3.3", - "semver": "5.3.0" - }, - "dependencies": { - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true - } - } - }, - "eslint-plugin-prebid": { - "version": "file:plugins/eslint", - "dev": true - }, - "eslint-plugin-promise": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.8.0.tgz", - "integrity": "sha512-JiFL9UFR15NKpHyGii1ZcvmtIqa3UTwiDAGb8atSffe43qJ3+1czVGN6UtkklpcJ2DVnqvTMzEKRaJdBkAL2aQ==", - "dev": true - }, - "eslint-plugin-standard": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz", - "integrity": "sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w==", - "dev": true - }, - "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", - "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", "dev": true, "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" } }, - "eventemitter3": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", - "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", - "dev": true - }, - "events": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", - "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" + "is-obj": "^2.0.0" } }, - "exec-sh": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", - "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", - "dev": true - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dotgitignore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", + "integrity": "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "find-up": "^3.0.0", + "minimatch": "^3.0.4" }, "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "locate-path": "^3.0.0" } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "p-limit": "^2.0.0" } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true } } }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dset/-/dset-2.0.1.tgz", + "integrity": "sha512-nI29OZMRYq36hOcifB6HTjajNAAiBKSXsyWZrq+VniusseuP2OpNlTiYgsaNRSGvpyq5Wjbc2gQLyBdTyWqhnQ==" + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, "requires": { - "fill-range": "^2.1.0" + "readable-stream": "^2.0.2" }, "dependencies": { - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "isarray": "1.0.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "safe-buffer": "~5.1.0" } } } }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "expect": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.1.0.tgz", - "integrity": "sha512-QbH4LZXDsno9AACrN9eM0zfnby9G+OsdNgZUohjg/P0mLy1O+/bzTAJGT6VSIjVCe8yKM6SzEl/ckEOFBT7Vnw==", + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", "dev": true, "requires": { - "@jest/types": "^26.1.0", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.0.0", - "jest-matcher-utils": "^26.1.0", - "jest-message-util": "^26.1.0", - "jest-regex-util": "^26.0.0" + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "color-name": "~1.1.4" + "safe-buffer": "~5.1.0" } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true } } }, - "expect-webdriverio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-1.2.0.tgz", - "integrity": "sha512-nis1EL4LJSKvhqES6ojx1QqAZYtWAUHaVtwilXBXJELN2YZhwOcrfBT0AvxDvJXYKzgDDTED9STc9MwcK8KbYg==", + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", "dev": true, "requires": { - "expect": "^26.0.1", - "jest-matcher-utils": "^26.0.1" - } - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" } }, - "ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "easy-table": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.1.tgz", + "integrity": "sha512-C9Lvm0WFcn2RgxbMnTbXZenMIWcBtkzMr+dWqq/JsVoGFSVUVlPqeOa5LP5kM0I3zoOazFpckOEb2/0LDFfToQ==", "dev": true, "requires": { - "type": "^2.0.0" + "ansi-regex": "^3.0.0", + "wcwidth": ">=1.0.1" }, "dependencies": { - "type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", - "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true } } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "edge-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", + "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } + "@types/which": "^1.3.2", + "which": "^2.0.2" } }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "ejs": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", + "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", "dev": true, "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" + "jake": "^10.6.1" } }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "electron-to-chromium": { + "version": "1.3.755", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.755.tgz", + "integrity": "sha512-BJ1s/kuUuOeo1bF/EM2E4yqW9te0Hpof3wgwBx40AWJE18zsD1Tqo0kr7ijnOc+lRsrlrqKPauJAHqaxOItoUA==", + "dev": true + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true } } }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.1.tgz", + "integrity": "sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.0", + "ws": "~7.4.2" }, "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "dev": true + }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -8981,4620 +40335,5702 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, + "requires": {} } } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "engine.io-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", + "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", + "dev": true, + "requires": { + "base64-arraybuffer": "0.1.4" + } }, - "faker": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/faker/-/faker-3.1.0.tgz", - "integrity": "sha1-D5CPr05uwCUk5UpX5DLFwBPgjJ8=", - "dev": true + "enhanced-resolve": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", + "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "object-assign": "^4.0.1", + "tapable": "^0.2.7" + } }, - "fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" + "ansi-colors": "^4.1.1" } }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", "dev": true }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", "dev": true }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", + "dev": true, + "requires": { + "string-template": "~0.2.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } }, - "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "es-abstract": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", "dev": true, "requires": { - "websocket-driver": ">=0.5.1" + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.10.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" } }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "es-get-iterator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", + "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", "dev": true, "requires": { - "bser": "2.1.1" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.0", + "has-symbols": "^1.0.1", + "is-arguments": "^1.1.0", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } } }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { - "pend": "~1.2.0" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" } }, - "fibers": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fibers/-/fibers-4.0.3.tgz", - "integrity": "sha512-MW5VrDtTOLpKK7lzw4qD7Z9tXaAhdOmOED5RHzg3+HjUk+ibkjVW0Py2ERtdqgTXaerLkVkBy2AEmJiT6RMyzg==", + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "dev": true, "requires": { - "detect-libc": "^1.0.3" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" } }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "es5-shim": { + "version": "4.5.15", + "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.5.15.tgz", + "integrity": "sha512-FYpuxEjMeDvU4rulKqFdukQyZSTpzhg4ScQHrAosrlVpR6GFyaw14f74yn2+4BugniIS0Frpg7TvwZocU4ZMTw==", + "dev": true + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" } }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" } }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, - "optional": true + "requires": { + "es6-promise": "^4.0.3" + } }, - "filelist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", - "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", "dev": true, "requires": { - "minimatch": "^3.0.4" + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + }, + "dependencies": { + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + } } }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } }, - "fileset": { + "es6-weak-map": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", "dev": true, "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" } }, - "filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", "dev": true, "requires": { - "to-regex-range": "^5.0.1" + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } } }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "eslint": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==", "dev": true, "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "color-convert": "^2.0.1" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "sprintf-js": "~1.0.2" } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "find-up": "^3.0.0" + "color-name": "~1.1.4" } - } - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "ms": "2.1.2" } }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "globals": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "type-fest": "^0.20.2" } }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "yallist": "^4.0.0" } - } - } - }, - "fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - } - }, - "flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true - }, - "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } - }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, - "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" - }, - "dependencies": { - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { - "glob": "^7.1.3" + "lru-cache": "^6.0.0" } - } - } - }, - "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "has-flag": "^4.0.0" } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, - "follow-redirects": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz", - "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==", - "dev": true - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "eslint-config-standard": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", + "integrity": "sha1-wGHk0GbzedwXzVYsZOgZtN1FRZE=", + "dev": true, + "requires": {} }, - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "eslint-import-resolver-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "dev": true, "requires": { - "for-in": "^1.0.1" + "debug": "^2.6.9", + "resolve": "^1.13.1" } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "foreachasync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", - "integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY=", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "fork-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", - "integrity": "sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "eslint-module-utils": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.1.tgz", + "integrity": "sha512-ZXI9B8cxAJIH4nfkhTwcRTEAnrVfobYqwjWy/QMCZ8rHkZHFjf9yO4BzpiF9kCSfNlMG54eKigISHpX0+AaT4A==", "dev": true, "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "debug": "^3.2.7", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + } } }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", "dev": true, "requires": { - "map-cache": "^0.2.2" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" } }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "eslint-plugin-import": { + "version": "2.23.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz", + "integrity": "sha512-6/wP8zZRsnQFiR3iaPFgh5ImVRM1WN5NUWfTIRqwOdeiGJlBcSk82o1FEVq8yXmy4lkIzTo7YhHCIxlU/2HyEQ==", "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "array-includes": "^3.1.3", + "array.prototype.flat": "^1.2.4", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-module-utils": "^2.6.1", + "find-up": "^2.0.0", + "has": "^1.0.3", + "is-core-module": "^2.4.0", + "minimatch": "^3.0.4", + "object.values": "^1.1.3", + "pkg-up": "^2.0.0", + "read-pkg-up": "^3.0.0", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.9.0" }, "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", - "dev": true, - "requires": { - "null-check": "^1.0.0" - } - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" - } - }, - "fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } } }, - "fs.extra": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz", - "integrity": "sha1-3QI/kwE77iRTHxszUUw3sg/ZM0k=", - "dev": true, + "eslint-plugin-prebid": { + "version": "file:plugins/eslint", "requires": { - "fs-extra": "~0.6.1", - "mkdirp": "~0.3.5", - "walk": "^2.3.9" + "beefy": "^1.0.0", + "brfs": "0.0.9", + "browserify": "^8.1.3", + "chai": "^1.9.1", + "chalk": "^1.0.0", + "concat-stream": "^1.4.6", + "coveralls": "2.11.2", + "dateformat": "^1.0.8", + "debug": "^2.1.1", + "doctrine": "^0.6.2", + "escape-string-regexp": "^1.0.2", + "escope": "^3.2.0", + "espree": "^2.2.0", + "esprima": "^2.4.1", + "esprima-fb": "^10001.1.0-dev-harmony-fb", + "estraverse": "^4.1.0", + "estraverse-fb": "^1.3.1", + "gh-got": "^1.0.3", + "globals": "^8.2.0", + "inquirer": "^0.8.2", + "is-my-json-valid": "^2.10.0", + "istanbul": "^0.3.5", + "js-yaml": "^3.2.5", + "jsdoc": "^3.3.0-beta1", + "jsonlint": "^1.6.2", + "lodash.clonedeep": "^3.0.1", + "lodash.merge": "^3.3.2", + "lodash.omit": "^3.1.0", + "markdownlint": "^0.0.6", + "minimatch": "^2.0.1", + "mkdirp": "^0.5.0", + "mocha": "^2.1.0", + "mocha-phantomjs": "3.6.0", + "npm-license": "^0.2.3", + "object-assign": "^2.0.0", + "optionator": "^0.5.0", + "path-is-absolute": "^1.0.0", + "path-is-inside": "^1.0.1", + "phantomjs": "1.9.7-15", + "proxyquire": "^1.0.0", + "rewire": "^2.3.4", + "semver": "^4.1.0", + "shelljs": "^0.3.0", + "shelljs-nodecli": "~0.1.0", + "sinon": "1.14.1", + "strip-json-comments": "~1.0.1", + "text-table": "~0.2.0", + "through": "^2.3.6", + "user-home": "^1.0.0", + "xml-escape": "~1.0.0" }, "dependencies": { - "fs-extra": { + "ansi-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", + "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true, + "optional": true + }, + "assertion-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true, + "optional": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "chai": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-1.10.0.tgz", + "integrity": "sha1-5AMcyHZURhp1lD5aNatG6vOcHrk=", + "dev": true, + "requires": { + "assertion-error": "1.0.0", + "deep-eql": "0.1.3" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cli-width": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz", + "integrity": "sha1-pNKT72frt7iNSk1CwMzwDE0eNm0=", + "dev": true + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "optional": true, + "requires": { + "delayed-stream": "0.0.5" + } + }, + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "coveralls": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-2.11.2.tgz", + "integrity": "sha1-1NmCAWyy+dqJ13qyBNhqhTfmoS0=", + "dev": true, + "requires": { + "js-yaml": "3.0.1", + "lcov-parse": "0.0.6", + "log-driver": "1.2.4", + "request": "2.40.0" + }, + "dependencies": { + "argparse": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "dev": true, + "requires": { + "underscore": "~1.7.0", + "underscore.string": "~2.4.0" + } + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "js-yaml": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.0.1.tgz", + "integrity": "sha1-dkBf6lvOMPyPQF1Ixtyn8KMsav4=", + "dev": true, + "requires": { + "argparse": "~ 0.1.11", + "esprima": "~ 1.0.2" + } + } + } + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + } + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + } + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true, + "optional": true + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "doctrine": { "version": "0.6.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", - "integrity": "sha1-9G8MdbeEH40gCzNIzU1pHVoJnRU=", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.6.4.tgz", + "integrity": "sha1-gUKEkalC7xiwSSBW7aOADu5X1h0=", + "dev": true, + "requires": { + "esutils": "^1.1.6", + "isarray": "0.0.1" + } + }, + "escodegen": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.1.tgz", + "integrity": "sha1-MOz89mypjcZ80v0WKr626vqM5vw=", + "dev": true, + "requires": { + "esprima": "^1.2.2", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.5.0", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", + "integrity": "sha1-CZNQL+r2aBODJXVvMPmlH+7sEek=", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + } + } + }, + "espree": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/espree/-/espree-2.2.5.tgz", + "integrity": "sha1-32kbkxCIlAKuspzAZnCMVmkLhUs=", + "dev": true + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "esutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz", + "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=", + "dev": true + }, + "fast-levenshtein": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz", + "integrity": "sha1-AXjc3uAjuSkFGTrwlZ6KdjnP3Lk=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=", + "dev": true + }, + "form-data": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", "dev": true, + "optional": true, "requires": { - "jsonfile": "~1.0.1", - "mkdirp": "0.3.x", - "ncp": "~0.4.2", - "rimraf": "~2.2.0" + "async": "~0.9.0", + "combined-stream": "~0.0.4", + "mime": "~1.2.11" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true, + "optional": true + } } }, - "jsonfile": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", - "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=", + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2", + "minimatch": "0.3" + }, + "dependencies": { + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "globals": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz", + "integrity": "sha1-k9SmK9ysOM+vr8R9awNHaMsP/LQ=", "dev": true }, - "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", "dev": true }, - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, - "fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", "dev": true, + "optional": true, "requires": { - "minimist": "^1.2.5" + "asn1": "0.1.11", + "assert-plus": "^0.1.5", + "ctype": "0.5.3" } - } - } - }, - "fun-hooks": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/fun-hooks/-/fun-hooks-0.9.9.tgz", - "integrity": "sha512-821UhoYfO9Sg01wAl/QsDRB088BW0aeOqzC1PXLxSlB+kaUVbK+Vp6wMDHU5huZZopYxmMmv5jDkEYqDpK3hqg==", - "requires": { - "typescript-tuple": "^2.2.1" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "requires": { - "globule": "^1.0.0" - } - }, - "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, - "get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", - "dev": true - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "git-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-2.1.0.tgz", - "integrity": "sha512-MJgwfcSd9qxgDyEYpRU/CDxNpUadrK80JHuEQDG4Urn0m7tpSOgCBrtiSIa9S9KH8Tbuo/TN8SSQmJBvsw1HkA==", - "dev": true, - "requires": { - "is-ssh": "^1.3.0", - "parse-url": "^3.0.2" - } - }, - "git-url-parse": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-8.3.1.tgz", - "integrity": "sha512-r/FxXIdfgdSO+V2zl4ZK1JGYkHT9nqVRSzom5WsYPLg3XzeBeKPl3R/6X9E9ZJRx/sE/dXwXtfl+Zp7YL8ktWQ==", - "dev": true, - "requires": { - "git-up": "^2.0.0", - "parse-domain": "^2.0.0" - } - }, - "github-slugger": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.2.0.tgz", - "integrity": "sha512-wIaa75k1vZhyPm9yWrD08A5Xnx/V+RmzGrpjQuLemGKSb77Qukiaei58Bogrl/LZSADDfPzKJX8jhLs4CRTl7Q==", - "dev": true, - "requires": { - "emoji-regex": ">=6.0.0 <=6.1.1" - }, - "dependencies": { - "emoji-regex": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", - "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=", + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "inquirer": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz", + "integrity": "sha1-29dAz2yjtzEpamPOb22WGFHzNt8=", + "dev": true, + "requires": { + "ansi-regex": "^1.1.1", + "chalk": "^1.0.0", + "cli-width": "^1.0.1", + "figures": "^1.3.5", + "lodash": "^3.3.1", + "readline2": "^0.1.1", + "rx": "^2.4.3", + "through": "^2.3.6" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true - } - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + }, + "istanbul": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.3.22.tgz", + "integrity": "sha1-PhZNhQIf4ZyYXR8OfvDD4i0BLrY=", "dev": true, "requires": { - "is-glob": "^2.0.0" + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.7.x", + "esprima": "2.5.x", + "fileset": "0.2.x", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "esprima": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.5.0.tgz", + "integrity": "sha1-84ekb9NEwbGjm6+MIL+0O20AWMw=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } } }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + } + } + }, + "lcov-parse": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.6.tgz", + "integrity": "sha1-gZ5dqL8HkfnT857qXtGGgYfxEXU=", + "dev": true + }, + "levn": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz", + "integrity": "sha1-uo0znQykphDjo/FFucr0iAcVUFQ=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.0", + "type-check": "~0.3.1" + } + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", "dev": true }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "lodash.clonedeep": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz", + "integrity": "sha1-oKHkDYKl6on/WxR7hETtY9koJ9s=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "lodash._baseclone": "^3.0.0", + "lodash._bindcallback": "^3.0.0" } - } - } - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", - "dev": true, - "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "dependencies": { - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + }, + "lodash.isplainobject": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz", + "integrity": "sha1-moI4rhayAEMpYM1zRlEtASP79MU=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "lodash._basefor": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.keysin": "^3.0.0" } }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" } }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "lodash.merge": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-3.3.2.tgz", + "integrity": "sha1-DZDZPtY3sYeEN7s+IWASYNev6ZQ=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "lodash._arraycopy": "^3.0.0", + "lodash._arrayeach": "^3.0.0", + "lodash._createassigner": "^3.0.0", + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0", + "lodash.isplainobject": "^3.0.0", + "lodash.istypedarray": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.keysin": "^3.0.0", + "lodash.toplainobject": "^3.0.0" } - } - } - }, - "glob-watcher": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", - "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "normalize-path": "^3.0.0", - "object.defaults": "^1.1.0" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + }, + "log-driver": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.4.tgz", + "integrity": "sha1-LWLX+u9F2KcTQZYaBLB2HsqZz6M=", + "dev": true + }, + "lolex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.1.0.tgz", + "integrity": "sha1-Xbu8hQOV51I8dLNYb3+9JibSWxs=", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" }, "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true } } }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true, + "optional": true + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=", "dev": true }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "brace-expansion": "^1.0.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "dev": true, + "requires": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "minimist": "0.0.8" } + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "dev": true } } }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "oauth-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", + "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } + "optional": true }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "object-assign": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", + "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", + "dev": true + }, + "optionator": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz", + "integrity": "sha1-t1qJlaLUF98ltuTjhi9QqohlE2g=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "deep-is": "~0.1.2", + "fast-levenshtein": "~1.0.0", + "levn": "~0.2.5", + "prelude-ls": "~1.1.1", + "type-check": "~0.3.1", + "wordwrap": "~0.0.2" }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true } } }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "qs": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-1.0.2.tgz", + "integrity": "sha1-UKk+K1r2aRwxvOpdrnjubqGQN2g=", + "dev": true + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, - "optional": true, "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" } }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "request": { + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.40.0.tgz", + "integrity": "sha1-TdZw9pbx5uhC5mtLXoOTAaub62c=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } + "aws-sign2": "~0.5.0", + "forever-agent": "~0.5.0", + "form-data": "~0.1.0", + "hawk": "1.1.1", + "http-signature": "~0.10.0", + "json-stringify-safe": "~5.0.0", + "mime-types": "~1.0.1", + "node-uuid": "~1.4.0", + "oauth-sign": "~0.3.0", + "qs": "~1.0.0", + "stringstream": "~0.0.4", + "tough-cookie": ">=0.12.0", + "tunnel-agent": "~0.4.0" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "dev": true + }, + "sinon": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.14.1.tgz", + "integrity": "sha1-2CeXhBkYc0UHyUt6c+P1YJBFeK0=", + "dev": true, + "requires": { + "formatio": "1.1.1", + "lolex": "1.1.0", + "util": ">=0.10.3 <1" } }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", "dev": true, + "optional": true, "requires": { - "binary-extensions": "^1.0.0" + "amdefine": ">=0.0.4" } }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "ansi-regex": "^2.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true } } }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "get-stdin": "^4.0.1" } }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } + "optional": true }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" + "prelude-ls": "~1.1.2" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + }, + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "isexe": "^2.0.0" } } } }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "eslint-plugin-promise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz", + "integrity": "sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==", + "dev": true, + "requires": {} + }, + "eslint-plugin-standard": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz", + "integrity": "sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" } }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globals-docs": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/globals-docs/-/globals-docs-2.4.1.tgz", - "integrity": "sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==", + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, - "globule": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", - "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { - "glob": "~7.1.1", - "lodash": "~4.17.10", - "minimatch": "~3.0.2" + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, - "glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esprima-fb": { + "version": "10001.1.0-dev-harmony-fb", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-10001.1.0-dev-harmony-fb.tgz", + "integrity": "sha1-9++0UtPIAG3eazxZZ4YE9xFKiCw=", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { - "sparkles": "^1.0.0" + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, - "got": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/got/-/got-11.5.1.tgz", - "integrity": "sha512-reQEZcEBMTGnujmQ+Wm97mJs/OK6INtO6HmLI+xt3+9CvnRwWjXutUvb2mqr+Ao4Lu05Rx6+udx9sOQAmExMxA==", + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "@sindresorhus/is": "^3.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.1", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.0", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "estraverse-fb": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/estraverse-fb/-/estraverse-fb-1.3.2.tgz", + "integrity": "sha1-0yOky15awzHOoDNBOpJT4WQ+B8Q=", + "dev": true, + "requires": {} + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "optional": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true }, - "gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" }, "dependencies": { - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "ansi-wrap": "^0.1.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" + "pump": "^3.0.0" } }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, - "gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "shebang-regex": "^1.0.0" } }, - "which-module": { + "shebang-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, - "yargs": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", - "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "5.0.0-security.0" - } - }, - "yargs-parser": { - "version": "5.0.0-security.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", - "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" + "isexe": "^2.0.0" } } } }, - "gulp-clean": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/gulp-clean/-/gulp-clean-0.3.2.tgz", - "integrity": "sha1-o0fUc6zqQBgvk1WHpFGUFnGSgQI=", + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "gulp-util": "^2.2.14", - "rimraf": "^2.2.8", - "through2": "^0.4.2" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", - "dev": true - }, - "ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", - "dev": true - }, - "chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", - "dev": true, - "requires": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" - } - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "gulp-util": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", - "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", - "dev": true, - "requires": { - "chalk": "^0.5.0", - "dateformat": "^1.0.7-1.2.3", - "lodash._reinterpolate": "^2.4.1", - "lodash.template": "^2.4.1", - "minimist": "^0.2.0", - "multipipe": "^0.1.0", - "through2": "^0.5.0", - "vinyl": "^0.2.1" - }, - "dependencies": { - "through2": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", - "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", - "dev": true, - "requires": { - "readable-stream": "~1.0.17", - "xtend": "~3.0.0" - } - } + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" } }, - "has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "ansi-regex": "^0.2.0" + "is-extendable": "^0.1.0" } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "minimist": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.1.tgz", - "integrity": "sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==", - "dev": true - }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + } + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "^2.1.0" + }, + "dependencies": { + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" } }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { - "ansi-regex": "^0.2.1" + "kind-of": "^3.0.2" } }, - "supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", - "dev": true - }, - "through2": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", - "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, "requires": { - "readable-stream": "~1.0.17", - "xtend": "~2.1.1" - }, - "dependencies": { - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", - "dev": true, - "requires": { - "object-keys": "~0.4.0" - } - } + "isarray": "1.0.0" } }, - "vinyl": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", - "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "clone-stats": "~0.0.1" + "is-buffer": "^1.1.5" } - }, - "xtend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", - "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", - "dev": true } } }, - "gulp-concat": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", - "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", "dev": true, "requires": { - "concat-with-sourcemaps": "^1.0.0", - "through2": "^2.0.0", - "vinyl": "^2.0.0" + "homedir-polyfill": "^1.0.1" } }, - "gulp-connect": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/gulp-connect/-/gulp-connect-5.7.0.tgz", - "integrity": "sha512-8tRcC6wgXMLakpPw9M7GRJIhxkYdgZsXwn7n56BA2bQYGLR9NOPhMzx7js+qYDy6vhNkbApGKURjAw1FjY4pNA==", + "expect": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.0.2.tgz", + "integrity": "sha512-YJFNJe2+P2DqH+ZrXy+ydRQYO87oxRUonZImpDodR1G7qo3NYd3pL+NQ9Keqpez3cehczYwZDBC3A7xk3n7M/w==", "dev": true, "requires": { - "ansi-colors": "^2.0.5", - "connect": "^3.6.6", - "connect-livereload": "^0.6.0", - "fancy-log": "^1.3.2", - "map-stream": "^0.0.7", - "send": "^0.16.2", - "serve-index": "^1.9.1", - "serve-static": "^1.13.2", - "tiny-lr": "^1.1.1" + "@jest/types": "^27.0.2", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.0.1", + "jest-matcher-utils": "^27.0.2", + "jest-message-util": "^27.0.2", + "jest-regex-util": "^27.0.1" }, "dependencies": { - "ansi-colors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-2.0.5.tgz", - "integrity": "sha512-yAdfUZ+c2wetVNIFsNRn44THW+Lty6S5TwMpUfLA/UaGhiXbBv/F8E60/1hMLd0cnF/CDoWH8vzVaI5bAcHCjw==", - "dev": true - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + } + } + }, + "expect-webdriverio": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-3.1.0.tgz", + "integrity": "sha512-Kn4Rtu5vKbDo95WNcjZ9XVz/qTPGZzumP9w7VSV4OxY5z6BAqSI2jb85EsqPxpavs33P+9Qse4Z+d5ilDD/dQw==", + "dev": true, + "requires": { + "expect": "^27.0.2", + "jest-matcher-utils": "^27.0.2" + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==", "dev": true - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "is-plain-object": "^2.0.4" } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true } } }, - "gulp-eslint": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-4.0.2.tgz", - "integrity": "sha512-fcFUQzFsN6dJ6KZlG+qPOEkqfcevRUXgztkYCvhNvJeSvOicC8ucutN4qR/ID8LmNZx9YPIkBzazTNnVvbh8wg==", + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, "requires": { - "eslint": "^4.0.0", - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.0" + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" } }, - "gulp-footer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/gulp-footer/-/gulp-footer-2.0.2.tgz", - "integrity": "sha512-HsG5VOgKHFRqZXnHGI6oGhPDg70p9pobM+dYOnjBZVLMQUHzLG6bfaPNRJ7XG707E+vWO3TfN0CND9UrYhk94g==", + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.6.2", - "map-stream": "0.0.7" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "lodash._root": "^3.0.0" + "is-descriptor": "^1.0.0" } }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" + "is-extendable": "^0.1.0" } }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" + "kind-of": "^6.0.0" } }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" + "kind-of": "^6.0.0" } }, - "map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", - "dev": true + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } } } }, - "gulp-header": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz", - "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==", + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, "requires": { - "concat-with-sourcemaps": "*", - "lodash.template": "^4.4.0", - "through2": "^2.0.0" + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" }, "dependencies": { - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" + "ms": "2.1.2" } }, - "lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0" - } + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==", + "dev": true + }, + "falafel": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/falafel/-/falafel-0.1.6.tgz", + "integrity": "sha1-MITPPUG1nRXIE75vJZVX/cgrBmA=", + "dev": true, + "requires": { + "esprima": "~1.0.2" + }, + "dependencies": { + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true } } }, - "gulp-if": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-2.0.2.tgz", - "integrity": "sha1-pJe351cwBQQcqivIt92jyARE1ik=", + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "fibers": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.0.tgz", + "integrity": "sha512-UpGv/YAZp7mhKHxDvC1tColrroGRX90sSvh8RMZV9leo+e5+EkRVgCEZPlmXeo3BUNQTZxUaVdLskq1Q2FyCPg==", + "dev": true, + "requires": { + "detect-libc": "^1.0.3" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { - "gulp-match": "^1.0.3", - "ternary-stream": "^2.0.1", - "through2": "^2.0.1" + "flat-cache": "^3.0.4" } }, - "gulp-js-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gulp-js-escape/-/gulp-js-escape-1.0.1.tgz", - "integrity": "sha1-HNRF+9AJ4Np2lZoDp/SbNWav+Gg=", + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "filelist": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", + "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", "dev": true, "requires": { - "through2": "^0.6.3" + "minimatch": "^3.0.4" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fileset": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.2.1.tgz", + "integrity": "sha1-WI74lzxmI7KnbfRlEFaWuWqsgGc=", + "dev": true, + "requires": { + "glob": "5.x", + "minimatch": "2.x" }, "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", "dev": true, "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "brace-expansion": "^1.0.0" } } } }, - "gulp-match": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.1.0.tgz", - "integrity": "sha512-DlyVxa1Gj24DitY2OjEsS+X6tDpretuxD6wTfhXE/Rw2hweqc1f6D/XtsJmoiCwLWfXgR87W9ozEityPCVzGtQ==", + "filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "dev": true + }, + "fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", "dev": true, "requires": { - "minimatch": "^3.0.3" + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" } }, - "gulp-replace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.0.0.tgz", - "integrity": "sha512-lgdmrFSI1SdhNMXZQbrC75MOl1UjYWlOWNbNRnz+F/KHmgxt3l6XstBoAYIdadwETFyG/6i+vWUSCawdC3pqOw==", + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "istextorbinary": "2.2.1", - "readable-stream": "^2.0.1", - "replacestream": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } + "to-regex-range": "^5.0.1" } }, - "gulp-shell": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/gulp-shell/-/gulp-shell-0.5.2.tgz", - "integrity": "sha1-pJWcoGUa0ce7/nCy0K27tOGuqY0=", - "dev": true, + "filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", + "dev": true + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "requires": { - "async": "^1.5.0", - "gulp-util": "^3.0.7", - "lodash": "^4.0.0", - "through2": "^2.0.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - } + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" } }, - "gulp-sourcemaps": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz", - "integrity": "sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg==", + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", "dev": true, "requires": { - "@gulp-sourcemaps/identity-map": "1.X", - "@gulp-sourcemaps/map-sources": "1.X", - "acorn": "5.X", - "convert-source-map": "1.X", - "css": "2.X", - "debug-fabulous": "1.X", - "detect-newline": "2.X", - "graceful-fs": "4.X", - "source-map": "~0.6.0", - "strip-bom-string": "1.X", - "through2": "2.X" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" } }, - "gulp-uglify": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.2.tgz", - "integrity": "sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg==", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "array-each": "^1.0.1", - "extend-shallow": "^3.0.2", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "isobject": "^3.0.1", - "make-error-cause": "^1.1.1", - "safe-buffer": "^5.1.2", - "through2": "^2.0.0", - "uglify-js": "^3.0.5", - "vinyl-sourcemaps-apply": "^0.2.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", "dev": true, "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "requires": { - "lodash._root": "^3.0.0" - } - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "object-assign": { + "is-number": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } } } }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", "dev": true, "requires": { - "glogg": "^1.0.0" + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" } }, - "gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "dev": true, - "requires": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } - } + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } } } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", "dev": true, "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" }, "dependencies": { - "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" } } } }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } + "follow-redirects": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, + "foreachasync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", + "integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "fork-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", + "integrity": "sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - } + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" } }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "formatio": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", + "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", "dev": true, "requires": { - "isarray": "2.0.1" + "samsam": "~1.1" }, "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "samsam": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.3.tgz", + "integrity": "sha1-n1CHQZtNCR8jJXHn+lLpCw9VJiE=", "dev": true } } }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "sparkles": "^1.0.0" + "map-cache": "^0.2.2" } }, - "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", - "dev": true + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", "dev": true, "requires": { - "has-symbol-support-x": "^1.4.1" + "null-check": "^1.0.0" } }, - "has-value": { + "fs-constants": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs-extra": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "dev": true, "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" } }, - "has-values": { + "fs-mkdirp-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" }, "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "safe-buffer": "~5.1.0" } }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "fs.extra": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz", + "integrity": "sha1-3QI/kwE77iRTHxszUUw3sg/ZM0k=", "dev": true, "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "fs-extra": "~0.6.1", + "mkdirp": "~0.3.5", + "walk": "^2.3.9" }, "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "fs-extra": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", + "integrity": "sha1-9G8MdbeEH40gCzNIzU1pHVoJnRU=", + "dev": true, + "requires": { + "jsonfile": "~1.0.1", + "mkdirp": "0.3.x", + "ncp": "~0.4.2", + "rimraf": "~2.2.0" + } + }, + "jsonfile": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", + "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=", "dev": true }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", "dev": true } } }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "fun-hooks": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/fun-hooks/-/fun-hooks-0.9.10.tgz", + "integrity": "sha512-7xBjdT+oMYOPWgwFxNiNzF4ubeUvim4zs1DnQqSSGyxu8UD7AW/6Z0iFsVRwuVSIZKUks2en2VHHotmNfj3ipw==", + "requires": { + "typescript-tuple": "^2.2.1" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "requires": { + "globule": "^1.0.0" + } + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "dev": true, "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" + "is-property": "^1.0.2" } }, - "hast-util-is-element": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.0.4.tgz", - "integrity": "sha512-NFR6ljJRvDcyPP5SbV7MyPBgF47X3BsskLnmw1U34yL+X6YC0MoBx9EyMg8Jtx4FzGH95jw8+c1VPLHaRA0wDQ==", - "dev": true - }, - "hast-util-sanitize": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-1.3.1.tgz", - "integrity": "sha512-AIeKHuHx0Wk45nSkGVa2/ujQYTksnDl8gmmKo/mwQi7ag7IBZ8cM3nJ2G86SajbjGP/HRpud6kMkPtcM2i0Tlw==", + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", "dev": true, "requires": { - "xtend": "^4.0.1" + "is-property": "^1.0.0" } }, - "hast-util-to-html": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-3.1.0.tgz", - "integrity": "sha1-iCyZhJ5AEw6ZHAQuRW1FPZXDbP8=", + "generic-names": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz", + "integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==", "dev": true, + "optional": true, "requires": { - "ccount": "^1.0.0", - "comma-separated-tokens": "^1.0.1", - "hast-util-is-element": "^1.0.0", - "hast-util-whitespace": "^1.0.0", - "html-void-elements": "^1.0.0", - "kebab-case": "^1.0.0", - "property-information": "^3.1.0", - "space-separated-tokens": "^1.0.0", - "stringify-entities": "^1.0.1", - "unist-util-is": "^2.0.0", - "xtend": "^4.0.1" + "loader-utils": "^1.1.0" }, "dependencies": { - "unist-util-is": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.3.tgz", - "integrity": "sha512-4WbQX2iwfr/+PfM4U3zd2VNXY+dWtZsN1fLnWEi2QQXA4qyDYAZcDMfXUX0Cu6XZUHHAO9q4nyxxLT4Awk1qUA==", - "dev": true + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "optional": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "optional": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } } } }, - "hast-util-whitespace": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz", - "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==", + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "highlight.js": { - "version": "9.18.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz", - "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==", + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "dev": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" } }, - "homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "get-pkg-repo": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz", + "integrity": "sha1-xztInAbYDMVTbCyFP54FIyBWly0=", "dev": true, "requires": { - "parse-passwd": "^1.0.0" + "hosted-git-info": "^2.1.4", + "meow": "^3.3.0", + "normalize-package-data": "^2.3.0", + "parse-github-repo-url": "^1.3.0", + "through2": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + } } }, - "hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", "dev": true }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", "dev": true }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { - "whatwg-encoding": "^1.0.1" + "pump": "^3.0.0" } }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, - "html-void-elements": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", - "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==", - "dev": true + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true + "gh-got": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-1.1.0.tgz", + "integrity": "sha1-UeIHrxkRPlBjM3I3zrMyhGmaSzs=", + "dev": true, + "requires": { + "got": "^3.2.0", + "object-assign": "^2.0.0" + }, + "dependencies": { + "got": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz", + "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=", + "dev": true, + "requires": { + "duplexify": "^3.2.0", + "infinity-agent": "^2.0.0", + "is-redirect": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "nested-error-stacks": "^1.0.0", + "object-assign": "^3.0.0", + "prepend-http": "^1.0.0", + "read-all-stream": "^3.0.0", + "timed-out": "^2.0.0" + }, + "dependencies": { + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + } + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "object-assign": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", + "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", + "dev": true + } + } }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "git-raw-commits": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.10.tgz", + "integrity": "sha512-sHhX5lsbG9SOO6yXdlwgEMQ/ljIn7qMpAbJZCGfXX2fq5T8M5SrDnpYk9/4HswTildcIqatsWa91vty6VhWSaQ==", + "dev": true, "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "dargs": "^7.0.0", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + } + }, + "git-remote-origin-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", + "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", + "dev": true, + "requires": { + "gitconfiglocal": "^1.0.0", + "pify": "^2.3.0" + } + }, + "git-semver-tags": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", + "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", + "dev": true, + "requires": { + "meow": "^8.0.0", + "semver": "^6.0.0" } }, - "http-parser-js": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", - "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==", - "dev": true + "git-up": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-4.0.2.tgz", + "integrity": "sha512-kbuvus1dWQB2sSW4cbfTeGpCMd8ge9jx9RKnhXhuJ7tnvT+NIrTVfYZxjtflZddQYcmdOTlkAcjmx7bor+15AQ==", + "dev": true, + "requires": { + "is-ssh": "^1.3.0", + "parse-url": "^5.0.0" + } }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "git-url-parse": { + "version": "11.4.4", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.4.4.tgz", + "integrity": "sha512-Y4o9o7vQngQDIU9IjyCmRJBin5iYjI5u9ZITnddRZpD7dcCFQj2sL2XuMNbLRE4b4B/4ENPsp2Q8P44fjAZ0Pw==", "dev": true, "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "git-up": "^4.0.0" } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "gitconfiglocal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", + "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", "dev": true, "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "ini": "^1.3.2" } }, - "http2-wrapper": { - "version": "1.0.0-beta.5.2", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz", - "integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==", + "github-slugger": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.2.0.tgz", + "integrity": "sha512-wIaa75k1vZhyPm9yWrD08A5Xnx/V+RmzGrpjQuLemGKSb77Qukiaei58Bogrl/LZSADDfPzKJX8jhLs4CRTl7Q==", "dev": true, "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" + "emoji-regex": ">=6.0.0 <=6.1.1" + }, + "dependencies": { + "emoji-regex": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", + "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=", + "dev": true + } } }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "github-url-from-git": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-url-from-git/-/github-url-from-git-1.5.0.tgz", + "integrity": "sha1-+YX+3MCpqledyI16/waNVcxiUaA=", "dev": true }, - "https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "github-url-from-username-repo": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/github-url-from-username-repo/-/github-url-from-username-repo-1.0.2.tgz", + "integrity": "sha1-fdeTMNKr5pwQws73lxTJchV5Hfo=", + "dev": true + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "dev": true, "requires": { - "agent-base": "5", - "debug": "4" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" }, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "ms": "^2.1.1" + "is-glob": "^2.0.0" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } } } }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "is-glob": "^4.0.1" } }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "dependencies": { + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", "dev": true, "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" }, "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, + "optional": true, "requires": { - "p-limit": "^2.0.0" + "bindings": "^1.5.0", + "nan": "^2.12.1" } }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "find-up": "^3.0.0" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } } - } - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "binary-extensions": "^1.0.0" } }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "color-name": "~1.1.4" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true - }, - "into-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", - "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", - "dev": true, - "requires": { - "from2": "^2.1.1", - "p-is-promise": "^1.1.0" - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } } } }, - "is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "dev": true - }, - "is-alphanumeric": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz", - "integrity": "sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=", - "dev": true - }, - "is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dev": true, - "requires": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-bigint": { + "global-modules": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", - "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", - "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", - "dev": true - }, - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { - "ci-info": "^2.0.0" + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" }, "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "isexe": "^2.0.0" } } } }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-docker": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-1.1.0.tgz", - "integrity": "sha1-8EN01O7lMQ6ajhE78UlUEeRhdqE=", - "dev": true - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "dev": true - }, - "is-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", - "dev": true - }, - "is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", - "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", - "dev": true - }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", - "dev": true - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, - "is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "globals-docs": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/globals-docs/-/globals-docs-2.4.1.tgz", + "integrity": "sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==", "dev": true }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "globule": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", + "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", "dev": true, "requires": { - "has": "^1.0.3" + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" } }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", "dev": true, "requires": { - "is-unc-path": "^1.0.0" + "sparkles": "^1.0.0" } }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "dev": true - }, - "is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", - "dev": true - }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", - "dev": true - }, - "is-ssh": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz", - "integrity": "sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg==", + "got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", "dev": true, "requires": { - "protocols": "^1.1.0" + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" } }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", "dev": true }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typed-array": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", - "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.0", - "es-abstract": "^1.17.4", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", "dev": true, "requires": { - "unc-path-regex": "^0.1.2" + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "dev": true + }, + "yargs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", + "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.1" + } + }, + "yargs-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", + "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + } } }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", - "dev": true - }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true - }, - "is-weakset": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", - "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", - "dev": true - }, - "is-whitespace-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", - "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-word-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", - "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "gulp-clean": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/gulp-clean/-/gulp-clean-0.3.2.tgz", + "integrity": "sha1-o0fUc6zqQBgvk1WHpFGUFnGSgQI=", "dev": true, "requires": { - "is-docker": "^2.0.0" + "gulp-util": "^2.2.14", + "rimraf": "^2.2.8", + "through2": "^0.4.2" }, "dependencies": { - "is-docker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", - "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", + "ansi-regex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "dev": true + }, + "ansi-styles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", + "dev": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "chalk": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "dev": true, + "requires": { + "ansi-styles": "^1.1.0", + "escape-string-regexp": "^1.0.0", + "has-ansi": "^0.1.0", + "strip-ansi": "^0.3.0", + "supports-color": "^0.2.0" + } + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "gulp-util": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", + "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", + "dev": true, + "requires": { + "chalk": "^0.5.0", + "dateformat": "^1.0.7-1.2.3", + "lodash._reinterpolate": "^2.4.1", + "lodash.template": "^2.4.1", + "minimist": "^0.2.0", + "multipipe": "^0.1.0", + "through2": "^0.5.0", + "vinyl": "^0.2.1" + }, + "dependencies": { + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + } + } + }, + "has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "dev": true, + "requires": { + "ansi-regex": "^0.2.0" + } + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, + "minimist": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.1.tgz", + "integrity": "sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==", + "dev": true + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "dev": true, + "requires": { + "ansi-regex": "^0.2.1" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true + }, + "through2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~2.1.1" + }, + "dependencies": { + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "vinyl": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", + "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + "dev": true, + "requires": { + "clone-stats": "~0.0.1" + } + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", "dev": true } } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isbinaryfile": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", - "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", + "gulp-concat": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", + "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", "dev": true, "requires": { - "buffer-alloc": "^1.2.0" + "concat-with-sourcemaps": "^1.0.0", + "through2": "^2.0.0", + "vinyl": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "gulp-connect": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/gulp-connect/-/gulp-connect-5.7.0.tgz", + "integrity": "sha512-8tRcC6wgXMLakpPw9M7GRJIhxkYdgZsXwn7n56BA2bQYGLR9NOPhMzx7js+qYDy6vhNkbApGKURjAw1FjY4pNA==", "dev": true, "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" + "ansi-colors": "^2.0.5", + "connect": "^3.6.6", + "connect-livereload": "^0.6.0", + "fancy-log": "^1.3.2", + "map-stream": "^0.0.7", + "send": "^0.16.2", + "serve-index": "^1.9.1", + "serve-static": "^1.13.2", + "tiny-lr": "^1.1.1" }, "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "ansi-colors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-2.0.5.tgz", + "integrity": "sha512-yAdfUZ+c2wetVNIFsNRn44THW+Lty6S5TwMpUfLA/UaGhiXbBv/F8E60/1hMLd0cnF/CDoWH8vzVaI5bAcHCjw==", "dev": true }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" } }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", "dev": true }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", "dev": true }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", "dev": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + } + } + }, + "gulp-eslint": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-4.0.2.tgz", + "integrity": "sha512-fcFUQzFsN6dJ6KZlG+qPOEkqfcevRUXgztkYCvhNvJeSvOicC8ucutN4qR/ID8LmNZx9YPIkBzazTNnVvbh8wg==", + "dev": true, + "requires": { + "eslint": "^4.0.0", + "fancy-log": "^1.3.2", + "plugin-error": "^1.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", "dev": true, - "optional": true, "requires": { - "amdefine": ">=0.0.4" + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } } }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "istanbul-api": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", - "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", - "dev": true, - "requires": { - "async": "^2.1.4", - "fileset": "^2.0.2", - "istanbul-lib-coverage": "^1.2.1", - "istanbul-lib-hook": "^1.2.2", - "istanbul-lib-instrument": "^1.10.2", - "istanbul-lib-report": "^1.1.5", - "istanbul-lib-source-maps": "^1.2.6", - "istanbul-reports": "^1.5.1", - "js-yaml": "^3.7.0", - "mkdirp": "^0.5.1", - "once": "^1.4.0" - }, - "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": {} + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "lodash": "^4.17.14" + "sprintf-js": "~1.0.2" } }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "ms": "^2.1.1" + "restore-cursor": "^2.0.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" + "ms": "^2.1.1" } }, - "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" + "esutils": "^2.0.2" } }, - "istanbul-lib-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", "dev": true, "requires": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" } }, - "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", "dev": true, "requires": { - "handlebars": "^4.0.3" + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", "dev": true, "requires": { - "minimist": "^1.2.5" + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { - "glob": "^7.1.3" + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" } }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "has-flag": "^1.0.0" + "escape-string-regexp": "^1.0.5" } - } - } - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", - "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", - "dev": true, - "requires": { - "append-transform": "^0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" - }, - "dependencies": { - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, "requires": { - "has-flag": "^3.0.0" + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" } - } - } - }, - "istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", "dev": true, "requires": { - "ms": "^2.1.1" + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" } }, - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { - "glob": "^7.1.3" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", - "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0" - } - }, - "istextorbinary": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.2.1.tgz", - "integrity": "sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==", - "dev": true, - "requires": { - "binaryextensions": "2", - "editions": "^1.3.3", - "textextensions": "2" - } - }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "dev": true, - "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - } - }, - "iterate-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", - "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", - "dev": true - }, - "iterate-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", - "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", - "dev": true, - "requires": { - "es-get-iterator": "^1.0.2", - "iterate-iterator": "^1.0.1" - } - }, - "jake": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", - "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", - "dev": true, - "requires": { - "async": "0.9.x", - "chalk": "^2.4.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - } - }, - "jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz", - "integrity": "sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==", - "dev": true, - "requires": { - "import-local": "^2.0.0", - "jest-cli": "^24.9.0" - }, - "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "minimist": "^1.2.5" } }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "mimic-fn": "^1.0.0" } }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, - "jest-cli": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.9.0.tgz", - "integrity": "sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==", - "dev": true, - "requires": { - "@jest/core": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "import-local": "^2.0.0", - "is-ci": "^2.0.0", - "jest-config": "^24.9.0", - "jest-util": "^24.9.0", - "jest-validate": "^24.9.0", - "prompts": "^2.0.1", - "realpath-native": "^1.1.0", - "yargs": "^13.3.0" - } + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "glob": "^7.1.3" } }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "shebang-regex": "^1.0.0" } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "is-fullwidth-code-point": "^2.0.0" } }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "ansi-regex": "^3.0.0" } }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" } - } - } - }, - "jest-changed-files": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz", - "integrity": "sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==", - "dev": true, - "requires": { - "@jest/types": "^24.9.0", - "execa": "^1.0.0", - "throat": "^4.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "prelude-ls": "~1.1.2" } }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "isexe": "^2.0.0" } } } }, - "jest-config": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz", - "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==", + "gulp-footer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/gulp-footer/-/gulp-footer-2.0.2.tgz", + "integrity": "sha512-HsG5VOgKHFRqZXnHGI6oGhPDg70p9pobM+dYOnjBZVLMQUHzLG6bfaPNRJ7XG707E+vWO3TfN0CND9UrYhk94g==", "dev": true, "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^24.9.0", - "@jest/types": "^24.9.0", - "babel-jest": "^24.9.0", - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^24.9.0", - "jest-environment-node": "^24.9.0", - "jest-get-type": "^24.9.0", - "jest-jasmine2": "^24.9.0", - "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.9.0", - "jest-util": "^24.9.0", - "jest-validate": "^24.9.0", - "micromatch": "^3.1.10", - "pretty-format": "^24.9.0", - "realpath-native": "^1.1.0" + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.6.2", + "map-stream": "0.0.7" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "lodash._root": "^3.0.0" } }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" } }, - "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", - "dev": true - }, - "jest-regex-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", - "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" } }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true } } }, - "jest-diff": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.1.0.tgz", - "integrity": "sha512-GZpIcom339y0OXznsEKjtkfKxNdg7bVbEofK8Q6MnevTIiR1jNhDWKhRX6X0SDXJlwn3dy59nZ1z55fLkAqPWg==", + "gulp-header": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-2.0.9.tgz", + "integrity": "sha512-LMGiBx+qH8giwrOuuZXSGvswcIUh0OiioNkUpLhNyvaC6/Ga8X6cfAeme2L5PqsbXMhL8o8b/OmVqIQdxprhcQ==", "dev": true, "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.0.0", - "jest-get-type": "^26.0.0", - "pretty-format": "^26.1.0" + "concat-with-sourcemaps": "^1.1.0", + "lodash.template": "^4.5.0", + "map-stream": "0.0.7", + "through2": "^2.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" } }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "lodash._reinterpolate": "^3.0.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "color-name": "~1.1.4" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } }, - "jest-docblock": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz", - "integrity": "sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==", - "dev": true, - "requires": { - "detect-newline": "^2.1.0" - } - }, - "jest-each": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz", - "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==", + "gulp-if": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-3.0.0.tgz", + "integrity": "sha512-fCUEngzNiEZEK2YuPm+sdMpO6ukb8+/qzbGfJBXyNOXz85bCG7yBI+pPSl+N90d7gnLvMsarthsAImx0qy7BAw==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.9.0", - "jest-util": "^24.9.0", - "pretty-format": "^24.9.0" + "gulp-match": "^1.1.0", + "ternary-stream": "^3.0.0", + "through2": "^3.0.1" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "inherits": "^2.0.4", + "readable-stream": "2 || 3" } } } }, - "jest-environment-jsdom": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz", - "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==", + "gulp-js-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gulp-js-escape/-/gulp-js-escape-1.0.1.tgz", + "integrity": "sha1-HNRF+9AJ4Np2lZoDp/SbNWav+Gg=", "dev": true, "requires": { - "@jest/environment": "^24.9.0", - "@jest/fake-timers": "^24.9.0", - "@jest/types": "^24.9.0", - "jest-mock": "^24.9.0", - "jest-util": "^24.9.0", - "jsdom": "^11.5.1" + "through2": "^0.6.3" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, "requires": { - "@types/yargs-parser": "*" + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" } } } }, - "jest-environment-node": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz", - "integrity": "sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==", + "gulp-match": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.1.0.tgz", + "integrity": "sha512-DlyVxa1Gj24DitY2OjEsS+X6tDpretuxD6wTfhXE/Rw2hweqc1f6D/XtsJmoiCwLWfXgR87W9ozEityPCVzGtQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.3" + } + }, + "gulp-replace": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", + "integrity": "sha512-HcPHpWY4XdF8zxYkDODHnG2+7a3nD/Y8Mfu3aBgMiCFDW3X2GiOKXllsAmILcxe3KZT2BXoN18WrpEFm48KfLQ==", "dev": true, "requires": { - "@jest/environment": "^24.9.0", - "@jest/fake-timers": "^24.9.0", - "@jest/types": "^24.9.0", - "jest-mock": "^24.9.0", - "jest-util": "^24.9.0" + "@types/node": "^14.14.41", + "@types/vinyl": "^2.0.4", + "istextorbinary": "^3.0.0", + "replacestream": "^4.0.3", + "yargs-parser": ">=5.0.0-security.0" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } + "@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true } } }, - "jest-get-type": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", - "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", - "dev": true - }, - "jest-haste-map": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", - "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", + "gulp-shell": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/gulp-shell/-/gulp-shell-0.8.0.tgz", + "integrity": "sha512-wHNCgmqbWkk1c6Gc2dOL5SprcoeujQdeepICwfQRo91DIylTE7a794VEE+leq3cE2YDoiS5ulvRfKVIEMazcTQ==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "anymatch": "^2.0.0", - "fb-watchman": "^2.0.0", - "fsevents": "^1.2.7", - "graceful-fs": "^4.1.15", - "invariant": "^2.2.4", - "jest-serializer": "^24.9.0", - "jest-util": "^24.9.0", - "jest-worker": "^24.9.0", - "micromatch": "^3.1.10", - "sane": "^4.0.3", - "walker": "^1.0.7" + "chalk": "^3.0.0", + "fancy-log": "^1.3.3", + "lodash.template": "^4.5.0", + "plugin-error": "^1.0.1", + "through2": "^3.0.1", + "tslib": "^1.10.0" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "color-convert": "^2.0.1" } }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "color-name": "~1.1.4" } }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "lodash._reinterpolate": "^3.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", "dev": true, - "optional": true, "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" + "inherits": "^2.0.4", + "readable-stream": "2 || 3" } + } + } + }, + "gulp-sourcemaps": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-3.0.0.tgz", + "integrity": "sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ==", + "dev": true, + "requires": { + "@gulp-sourcemaps/identity-map": "^2.0.1", + "@gulp-sourcemaps/map-sources": "^1.0.0", + "acorn": "^6.4.1", + "convert-source-map": "^1.0.0", + "css": "^3.0.0", + "debug-fabulous": "^1.0.0", + "detect-newline": "^2.0.0", + "graceful-fs": "^4.0.0", + "source-map": "^0.6.0", + "strip-bom-string": "^1.0.0", + "through2": "^2.0.0" + }, + "dependencies": { + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", "dev": true }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "safe-buffer": "~5.1.0" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } }, - "jest-jasmine2": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz", - "integrity": "sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==", + "gulp-terser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gulp-terser/-/gulp-terser-2.0.1.tgz", + "integrity": "sha512-XCrnCXP8ovNpgLK9McJIXlgm0j3W2TsiWu7K9y3m+Sn5XZgUzi6U8MPHtS3NdLMic9poCj695N0ARJ2B6atypw==", "dev": true, "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^24.9.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^24.9.0", - "jest-matcher-utils": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-runtime": "^24.9.0", - "jest-snapshot": "^24.9.0", - "jest-util": "^24.9.0", - "pretty-format": "^24.9.0", - "throat": "^4.0.0" + "plugin-error": "^1.0.1", + "terser": "5.4.0", + "through2": "^4.0.2", + "vinyl-sourcemaps-apply": "^0.2.1" + } + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "dev": true, + "requires": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "lodash._root": "^3.0.0" } }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", "dev": true, "requires": { - "@types/yargs-parser": "*" + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" } }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", "dev": true }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" } }, - "diff-sequences": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", - "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, - "expect": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", - "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "ansi-styles": "^3.2.0", - "jest-get-type": "^24.9.0", - "jest-matcher-utils": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-regex-util": "^24.9.0" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "^1.0.0" + } + }, + "gzip-size": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", + "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "dev": true, + "requires": { + "duplexer": "^0.1.1", + "pify": "^4.0.1" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true - }, + } + } + }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + } + } + }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, + "has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -13615,1408 +46051,1693 @@ } } }, - "jest-diff": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", - "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff-sequences": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" - } - }, - "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", - "dev": true - }, - "jest-matcher-utils": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz", - "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-diff": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" - } - }, - "jest-message-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", - "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-regex-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", - "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", - "dev": true, - "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", - "dev": true - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-buffer": "^1.1.5" } } } }, - "jest-leak-detector": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz", - "integrity": "sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==", + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "dev": true, "requires": { - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true - }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", - "dev": true, - "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - } } } }, - "jest-matcher-utils": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.1.0.tgz", - "integrity": "sha512-PW9JtItbYvES/xLn5mYxjMd+Rk+/kIt88EfH3N7w9KeOrHWaHrdYPnVHndGbsFGRJ2d5gKtwggCvkqbFDoouQA==", + "hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true, + "optional": true + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "dev": true, "requires": { - "chalk": "^4.0.0", - "jest-diff": "^26.1.0", - "jest-get-type": "^26.0.0", - "pretty-format": "^26.1.0" + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hast-util-is-element": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz", + "integrity": "sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ==", + "dev": true + }, + "hast-util-sanitize": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-3.0.2.tgz", + "integrity": "sha512-+2I0x2ZCAyiZOO/sb4yNLFmdwPBnyJ4PBkVTUMKMqBwYNA+lXSgOmoRXlJFazoyid9QPogRRKgKhVEodv181sA==", + "dev": true, + "requires": { + "xtend": "^4.0.0" + } + }, + "hast-util-to-html": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-7.1.3.tgz", + "integrity": "sha512-yk2+1p3EJTEE9ZEUkgHsUSVhIpCsL/bvT8E5GzmWc+N1Po5gBw+0F8bo7dpxXR0nu0bQVxVZGX2lBGF21CmeDw==", + "dev": true, + "requires": { + "ccount": "^1.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-is-element": "^1.0.0", + "hast-util-whitespace": "^1.0.0", + "html-void-elements": "^1.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0", + "stringify-entities": "^3.0.1", + "unist-util-is": "^4.0.0", + "xtend": "^4.0.0" + } + }, + "hast-util-whitespace": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz", + "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==", + "dev": true + }, + "hawk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", + "integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=", + "dev": true, + "optional": true, + "requires": { + "boom": "0.4.x", + "cryptiles": "0.2.x", + "hoek": "0.9.x", + "sntp": "0.2.x" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=", + "dev": true, + "optional": true + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "html-select": { + "version": "2.3.24", + "resolved": "https://registry.npmjs.org/html-select/-/html-select-2.3.24.tgz", + "integrity": "sha1-Rq1tcS5zLPMcZznV0BEKX6vxdYU=", + "dev": true, + "requires": { + "cssauron": "^1.1.0", + "duplexer2": "~0.0.2", + "inherits": "^2.0.1", + "minimist": "~0.0.8", + "readable-stream": "^1.0.27-1", + "split": "~0.3.0", + "stream-splicer": "^1.2.0", + "through2": "^1.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "readable-stream": "~1.1.9" } }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "color-name": "~1.1.4" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", "dev": true, "requires": { - "has-flag": "^4.0.0" + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" } } } }, - "jest-message-util": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.1.0.tgz", - "integrity": "sha512-dY0+UlldiAJwNDJ08SF0HdF32g9PkbF2NRK/+2iMPU40O6q+iSn1lgog/u0UH8ksWoPv0+gNq8cjhYO2MFtT0g==", + "html-tokenize": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-1.2.5.tgz", + "integrity": "sha1-flupnstR75Buyaf83ubKMmfHiX4=", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.1.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" + "inherits": "~2.0.1", + "minimist": "~0.0.8", + "readable-stream": "~1.0.27-1", + "through2": "~0.4.1" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "color-name": "~1.1.4" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "through2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~2.1.1" + } }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", "dev": true, "requires": { - "has-flag": "^4.0.0" + "object-keys": "~0.4.0" } } } }, - "jest-mock": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", - "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", + "html-void-elements": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", + "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==", + "dev": true + }, + "http-browserify": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz", + "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=", "dev": true, "requires": { - "@jest/types": "^24.9.0" - }, - "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } + "Base64": "~0.2.0", + "inherits": "~2.0.1" } }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", "dev": true }, - "jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-parser-js": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", + "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", "dev": true }, - "jest-resolve": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz", - "integrity": "sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==", + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "jest-pnp-resolver": "^1.2.1", - "realpath-native": "^1.1.0" - }, - "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" } }, - "jest-resolve-dependencies": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz", - "integrity": "sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==", + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "jest-regex-util": "^24.3.0", - "jest-snapshot": "^24.9.0" + "agent-base": "5", + "debug": "4" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "ms": "2.1.2" } }, - "jest-regex-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", - "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } }, - "jest-runner": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz", - "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==", + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true, + "optional": true + }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "optional": true, + "requires": {} + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ignorepatterns": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignorepatterns/-/ignorepatterns-1.0.1.tgz", + "integrity": "sha1-NPOeGlKP34tLS21ZNZypIfYUb6U=", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { - "@jest/console": "^24.7.1", - "@jest/environment": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "chalk": "^2.4.2", - "exit": "^0.1.2", - "graceful-fs": "^4.1.15", - "jest-config": "^24.9.0", - "jest-docblock": "^24.3.0", - "jest-haste-map": "^24.9.0", - "jest-jasmine2": "^24.9.0", - "jest-leak-detector": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-resolve": "^24.9.0", - "jest-runtime": "^24.9.0", - "jest-util": "^24.9.0", - "jest-worker": "^24.6.0", - "source-map-support": "^0.5.6", - "throat": "^4.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "jest-message-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", - "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } } } }, - "jest-runtime": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz", - "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==", + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "infinity-agent": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz", + "integrity": "sha1-ReDi/3qesDCyfWK3SzdEt6esQhY=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "inline-source-map": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.3.1.tgz", + "integrity": "sha1-pSi1FOaJ/OkNswiehw2S9Sestes=", "dev": true, "requires": { - "@jest/console": "^24.7.1", - "@jest/environment": "^24.9.0", - "@jest/source-map": "^24.3.0", - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/yargs": "^13.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.1.15", - "jest-config": "^24.9.0", - "jest-haste-map": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-mock": "^24.9.0", - "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.9.0", - "jest-snapshot": "^24.9.0", - "jest-util": "^24.9.0", - "jest-validate": "^24.9.0", - "realpath-native": "^1.1.0", - "slash": "^2.0.0", - "strip-bom": "^3.0.0", - "yargs": "^13.3.0" + "source-map": "~0.3.0" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "source-map": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.3.0.tgz", + "integrity": "sha1-hYb7mloAXltQHiHNGLbyG0V60fk=", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "amdefine": ">=0.0.4" } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + } + } + }, + "inquirer": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.1.1.tgz", + "integrity": "sha512-hUDjc3vBkh/uk1gPfMAD/7Z188Q8cvTGl0nxwaCdwSbzFh6ZKkZh+s2ozVxbE5G9ZNRyeY0+lgbAIOUFsFf98w==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.3.0", + "run-async": "^2.4.0", + "rxjs": "^6.6.6", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "color-convert": "^2.0.1" } }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "color-name": "~1.1.4" } }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "fill-range": { + "has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "jest-message-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", - "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-regex-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", - "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "has-flag": "^4.0.0" } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + } + } + }, + "insert-module-globals": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-6.6.3.tgz", + "integrity": "sha1-IGOOKaMPntHKLjqCX7wsulJG3fw=", + "dev": true, + "requires": { + "combine-source-map": "~0.6.1", + "concat-stream": "~1.4.1", + "is-buffer": "^1.1.0", + "JSONStream": "^1.0.3", + "lexical-scope": "^1.2.0", + "process": "~0.11.0", + "through2": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "combine-source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.6.1.tgz", + "integrity": "sha1-m0oJwxYDPXaODxHgKfonMOB5rZY=", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.5.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.4.2" } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", "dev": true }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "inline-source-map": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.5.0.tgz", + "integrity": "sha1-Skxd2OT7Xps82mDIIt+tyu5m4K8=", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "source-map": "~0.4.0" } }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "amdefine": ">=0.0.4" } }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true + }, + "is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, + "is-arguments": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", + "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", + "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", + "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-core-module": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", + "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" } } } }, - "jest-serializer": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", - "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==", + "is-date-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", + "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", + "dev": true + }, + "is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", "dev": true }, - "jest-snapshot": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz", - "integrity": "sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==", + "is-my-json-valid": { + "version": "2.20.6", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.6.tgz", + "integrity": "sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw==", "dev": true, "requires": { - "@babel/types": "^7.0.0", - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", - "expect": "^24.9.0", - "jest-diff": "^24.9.0", - "jest-get-type": "^24.9.0", - "jest-matcher-utils": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-resolve": "^24.9.0", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^24.9.0", - "semver": "^6.2.0" + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^5.0.0", + "xtend": "^4.0.0" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", + "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, + "is-regex": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", + "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.2" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-running": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", + "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", + "dev": true + }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true + }, + "is-ssh": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.3.tgz", + "integrity": "sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ==", + "dev": true, + "requires": { + "protocols": "^1.1.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-string": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", + "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", + "dev": true + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "dev": true, + "requires": { + "text-extensions": "^1.0.0" + } + }, + "is-typed-array": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", + "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.2", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.0-next.2", + "foreach": "^2.0.5", + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "is-weakset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "sprintf-js": "~1.0.2" } }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "diff-sequences": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", - "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", "dev": true }, - "expect": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", - "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", - "dev": true, - "requires": { - "@jest/types": "^24.9.0", - "ansi-styles": "^3.2.0", - "jest-get-type": "^24.9.0", - "jest-matcher-utils": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-regex-util": "^24.9.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { - "kind-of": "^3.0.2" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true } } }, - "jest-diff": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", - "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "chalk": "^2.0.1", - "diff-sequences": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" + "minimist": "^1.2.5" } }, - "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true }, - "jest-matcher-utils": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz", - "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==", + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "chalk": "^2.0.1", - "jest-diff": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" + "has-flag": "^1.0.0" } }, - "jest-message-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", - "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" + "isexe": "^2.0.0" } - }, - "jest-regex-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", - "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + } + } + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "has-flag": "^4.0.0" } - }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "ms": "2.1.2" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + } + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "istextorbinary": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-3.3.0.tgz", + "integrity": "sha512-Tvq1W6NAcZeJ8op+Hq7tdZ434rqnMx4CCZ7H0ff83uEloDvVbqAwaMTZcafKGJT0VHkYzuXUiCY4hlXQg6WfoQ==", + "dev": true, + "requires": { + "binaryextensions": "^2.2.0", + "textextensions": "^3.2.0" + } + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", "dev": true }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true } } }, - "jest-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", - "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", + "jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "dev": true, + "requires": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + } + }, + "jest-diff": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.2.tgz", + "integrity": "sha512-BFIdRb0LqfV1hBt8crQmw6gGQHVDhM87SpMIZ45FPYKReZYG5er1+5pIn2zKqvrJp6WNox0ylR8571Iwk2Dmgw==", "dev": true, "requires": { - "@jest/console": "^24.9.0", - "@jest/fake-timers": "^24.9.0", - "@jest/source-map": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "callsites": "^3.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.15", - "is-ci": "^2.0.0", - "mkdirp": "^0.5.1", - "slash": "^2.0.0", - "source-map": "^0.6.0" + "chalk": "^4.0.0", + "diff-sequences": "^27.0.1", + "jest-get-type": "^27.0.1", + "pretty-format": "^27.0.2" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "color-convert": "^2.0.1" } }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "minimist": "^1.2.5" + "color-name": "~1.1.4" } }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, - "jest-validate": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz", - "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==", + "jest-get-type": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", + "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", + "dev": true + }, + "jest-matcher-utils": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.0.2.tgz", + "integrity": "sha512-Qczi5xnTNjkhcIB0Yy75Txt+Ez51xdhOxsukN7awzq2auZQGPHcQrJ623PZj0ECDEMOk2soxWx05EXdXGd1CbA==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "camelcase": "^5.3.1", - "chalk": "^2.0.1", - "jest-get-type": "^24.9.0", - "leven": "^3.1.0", - "pretty-format": "^24.9.0" + "chalk": "^4.0.0", + "jest-diff": "^27.0.2", + "jest-get-type": "^27.0.1", + "pretty-format": "^27.0.2" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "color-convert": "^2.0.1" } }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "has-flag": "^4.0.0" } } } }, - "jest-watcher": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.9.0.tgz", - "integrity": "sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==", + "jest-message-util": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.0.2.tgz", + "integrity": "sha512-rTqWUX42ec2LdMkoUPOzrEd1Tcm+R1KfLOmFK+OVNo4MnLsEaxO5zPDb2BbdSmthdM/IfXxOZU60P/WbWF8BTw==", "dev": true, "requires": { - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/yargs": "^13.0.0", - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "jest-util": "^24.9.0", - "string-length": "^2.0.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.0.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "color-convert": "^2.0.1" } }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true - } - } - }, - "jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", - "dev": true, - "requires": { - "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" - }, - "dependencies": { + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } } } }, + "jest-regex-util": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.1.tgz", + "integrity": "sha512-6nY6QVcpTgEKQy1L41P4pr3aOddneK17kn3HJw6SdwGiKfgCGTvH02hVXL0GU8GEKtPH83eD2DIDgxHXOxVohQ==", + "dev": true + }, + "jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -15024,13 +47745,21 @@ "dev": true }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" + } + }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.4" } }, "jsbn": { @@ -15039,56 +47768,36 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" + "jsdoc": { + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.7.tgz", + "integrity": "sha512-sxKt7h0vzCd+3Y81Ey2qinupL6DpRSZJclS04ugHDNmRUXGzqicMJ6iwayhSA0S0DwwX30c5ozyUthr1QKF6uw==", + "dev": true, + "requires": { + "@babel/parser": "^7.9.4", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.1", + "klaw": "^3.0.0", + "markdown-it": "^10.0.0", + "markdown-it-anchor": "^5.2.7", + "marked": "^2.0.3", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.1" }, "dependencies": { - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true } } }, - "jsencrypt": { - "version": "3.0.0-rc.1", - "resolved": "https://registry.npmjs.org/jsencrypt/-/jsencrypt-3.0.0-rc.1.tgz", - "integrity": "sha512-gcvGaqerlUJy1Kq6tNgPYteVEoWNemu+9hBe2CdsCIz4rVcwjoTQ72iD1W76/PRMlnkzG0yVh7nwOOMOOUfKmg==" - }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -15113,6 +47822,21 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=", + "dev": true, + "requires": { + "jju": "^1.1.0" + } + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -15125,6 +47849,15 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -15138,22 +47871,38 @@ "dev": true }, "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, "requires": { "minimist": "^1.2.5" } }, "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" + "universalify": "^2.0.0" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonlint": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.3.tgz", + "integrity": "sha512-jMVTMzP+7gU/IyC6hvKyWpUU8tmTkK5b3BPNuMI9U8Sit+YAWLlZwB6Y6YrdCxfg2kNz05p3XY3Bmm4m26Nv3A==", + "dev": true, + "requires": { + "JSV": "^4.0.x", + "nomnom": "^1.5.x" } }, "jsonparse": { @@ -15162,6 +47911,22 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, + "jsonpointer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", + "integrity": "sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -15174,67 +47939,70 @@ "verror": "1.10.0" } }, + "JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=", + "dev": true + }, "just-clone": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-1.0.2.tgz", "integrity": "sha1-v7P672WqEqMWBYcSlFwyb9jwFDQ=" }, "just-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", - "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", + "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", "dev": true }, "just-extend": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", - "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, "karma": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-4.4.1.tgz", - "integrity": "sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.4.tgz", + "integrity": "sha512-hbhRogUYIulfkBTZT7xoPrCYhRBnBoqbbL4fszWD0ReFGUxU+LYBr3dwKdAluaDQ/ynT9/7C+Lf7pPNW4gSx4Q==", "dev": true, "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", + "body-parser": "^1.19.0", "braces": "^3.0.2", - "chokidar": "^3.0.0", - "colors": "^1.1.0", - "connect": "^3.6.0", + "chokidar": "^3.5.1", + "colors": "^1.4.0", + "connect": "^3.7.0", "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "flatted": "^2.0.0", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^4.17.14", - "log4js": "^4.0.0", - "mime": "^2.3.1", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", - "socket.io": "2.1.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.3.0", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^3.1.0", "source-map": "^0.6.1", - "tmp": "0.0.33", - "useragent": "2.3.0" + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.28", + "yargs": "^16.1.1" }, "dependencies": { "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", "dev": true }, "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -15245,17 +48013,39 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } } } }, "karma-babel-preprocessor": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/karma-babel-preprocessor/-/karma-babel-preprocessor-6.0.1.tgz", - "integrity": "sha1-euHT5klQ2+EfQht0BAqwj7WmbCE=", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/karma-babel-preprocessor/-/karma-babel-preprocessor-8.0.1.tgz", + "integrity": "sha512-5upyawNi3c7Gg6tPH1FWRVTmUijGf3v1GV4ScLM/2jKdDP18SlaKlUpu8eJrRI3STO8qK1bkqFcdgAA364nLYQ==", "dev": true, - "requires": { - "babel-core": "^6.0.0" - } + "requires": {} }, "karma-browserstack-launcher": { "version": "1.4.0", @@ -15272,86 +48062,94 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", "integrity": "sha1-vuWtQEAFF4Ea40u5RfdikJEIt5o=", - "dev": true + "dev": true, + "requires": {} }, "karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", + "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", "dev": true, "requires": { - "fs-access": "^1.0.0", "which": "^1.2.1" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "karma-coverage": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.0.2.tgz", - "integrity": "sha512-zge5qiGEIKDdzWciQwP4p0LSac4k/L6VfrBsERMUn5mpDvxhv1sPVOrSlpzpi70T7NhuEy4bgnpAKIYuumIMCw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.0.3.tgz", + "integrity": "sha512-atDvLQqvPcLxhED0cmXYdsPMCQuh6Asa9FMZW1bhNqlVEhJoB9qyZ2BY1gu7D/rr5GLGb5QzYO4siQskxaWP/g==", "dev": true, "requires": { "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^4.0.1", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.0", + "minimatch": "^3.0.4" + } + }, + "karma-coverage-istanbul-reporter": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-3.0.3.tgz", + "integrity": "sha512-wE4VFhG/QZv2Y4CdAYWDbMmcAHeS926ZIji4z+FkB2aF/EposRb6DP6G5ncT/wXhqUfAb/d7kZrNKPonbvsATw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^3.0.2", "minimatch": "^3.0.4" }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" + "ms": "2.1.2" } }, "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", "dev": true, "requires": { "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + } } }, "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "semver": "^6.0.0" + "pify": "^4.0.1", + "semver": "^5.6.0" } }, "ms": { @@ -15360,10 +48158,25 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "source-map": { @@ -15371,28 +48184,9 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, - "karma-coverage-istanbul-reporter": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-1.4.3.tgz", - "integrity": "sha1-O13/RmT6W41RlrmInj9hwforgNk=", - "dev": true, - "requires": { - "istanbul-api": "^1.3.1", - "minimatch": "^3.0.4" - } - }, "karma-es5-shim": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/karma-es5-shim/-/karma-es5-shim-0.0.4.tgz", @@ -15403,12 +48197,13 @@ } }, "karma-firefox-launcher": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.3.0.tgz", - "integrity": "sha512-Fi7xPhwrRgr+94BnHX0F5dCl1miIW4RHnzjIGxF8GaIEp7rNqX7LSi7ok63VXs3PS/5MQaQMhGxw+bvD+pibBQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.1.tgz", + "integrity": "sha512-VzDMgPseXak9DtfyE1O5bB2BwsMy1zzO1kUxVW1rP0yhC4tDNJ0p3JoFdzvrK4QqVzdqUMa9Rx9YzkdFp8hz3Q==", "dev": true, "requires": { - "is-wsl": "^2.1.0" + "is-wsl": "^2.2.0", + "which": "^2.0.1" } }, "karma-ie-launcher": { @@ -15421,20 +48216,12 @@ } }, "karma-mocha": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", - "integrity": "sha1-7qrH/8DiAetjxGdEDStpx883eL8=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", + "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", "dev": true, "requires": { - "minimist": "1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } + "minimist": "^1.2.3" } }, "karma-mocha-reporter": { @@ -15478,39 +48265,43 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/karma-opera-launcher/-/karma-opera-launcher-1.0.0.tgz", "integrity": "sha1-+lFihTGh0L6EstjcDX7iCfyP+Ro=", - "dev": true + "dev": true, + "requires": {} }, "karma-safari-launcher": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz", "integrity": "sha1-lpgqLMR9BmquccVTursoMZEVos4=", - "dev": true + "dev": true, + "requires": {} }, "karma-script-launcher": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/karma-script-launcher/-/karma-script-launcher-1.0.0.tgz", "integrity": "sha1-zQF8TeXvCeWp2nkydhdhCN1LVC0=", - "dev": true + "dev": true, + "requires": {} }, "karma-sinon": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/karma-sinon/-/karma-sinon-1.0.5.tgz", "integrity": "sha1-TjRD8oMP3s/2JNN0cWPxIX2qKpo=", - "dev": true + "dev": true, + "requires": {} }, "karma-sourcemap-loader": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.7.tgz", - "integrity": "sha1-kTIsd/jxPUb+0GKwQuEAnUxFBdg=", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.8.tgz", + "integrity": "sha512-zorxyAakYZuBcHRJE+vbrK2o2JXLFWK8VVjiT/6P+ltLBUGUvqTEkUiQ119MGdOrK7mrmxXHZF1/pfT6GgIZ6g==", "dev": true, "requires": { "graceful-fs": "^4.1.2" } }, "karma-spec-reporter": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.31.tgz", - "integrity": "sha1-SDDccUihVcfXoYbmMjOaDYD63sM=", + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.32.tgz", + "integrity": "sha1-LpxyB+pyZ3EmAln4K+y1QyCeRAo=", "dev": true, "requires": { "colors": "^1.1.2" @@ -15561,16 +48352,16 @@ } } }, - "kebab-case": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.0.tgz", - "integrity": "sha1-P55JkK3K0MaGwOcB92RYaPdfkes=", + "kew": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.1.7.tgz", + "integrity": "sha1-CjKoF/8amzsSuMm6z0vE1nmvjnI=", "dev": true }, "keyv": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.1.tgz", - "integrity": "sha512-xz6Jv6oNkbhrFCvCP7HQa8AaII8y8LRpoSm661NOKLr4uHuBwhX4epXrPQgF3+xdJnN4Esm5X0xwY4bOlALOtw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", "dev": true, "requires": { "json-buffer": "3.0.1" @@ -15582,11 +48373,43 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "konan": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", + "integrity": "sha512-7ZhYV84UzJ0PR/RJnnsMZcAbn+kLasJhVNWsu8ZyVEJYRpGA5XESQ9d/7zOa08U0Ou4cmB++hMNY/3OSV9KIbg==", + "dev": true, + "requires": { + "@babel/parser": "^7.10.5", + "@babel/traverse": "^7.10.5" + } + }, + "labeled-stream-splicer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-1.0.2.tgz", + "integrity": "sha1-RhUzFTd4SYHo/SZOHzpDTE4N3WU=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "isarray": "~0.0.1", + "stream-splicer": "^1.1.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } }, "last-run": { "version": "1.1.1", @@ -15627,6 +48450,15 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -15654,35 +48486,23 @@ "flush-write-stream": "^1.0.2" } }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { - "leven": "^3.1.0" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "lexical-scope": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", + "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "astw": "^2.0.0" } }, "liftoff": { @@ -15711,6 +48531,21 @@ "marky": "^1.2.0" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, "listenercount": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", @@ -15776,12 +48611,113 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash._arraycopy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", + "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=", + "dev": true + }, + "lodash._arrayeach": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", + "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=", + "dev": true + }, + "lodash._arraymap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraymap/-/lodash._arraymap-3.0.0.tgz", + "integrity": "sha1-Go/Q9MDfS2HeoHbXF83Jfwo8PmY=", + "dev": true + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + }, + "dependencies": { + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + } + } + }, + "lodash._baseclone": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", + "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", + "dev": true, + "requires": { + "lodash._arraycopy": "^3.0.0", + "lodash._arrayeach": "^3.0.0", + "lodash._baseassign": "^3.0.0", + "lodash._basefor": "^3.0.0", + "lodash.isarray": "^3.0.0", + "lodash.keys": "^3.0.0" + }, + "dependencies": { + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + } + } + }, "lodash._basecopy": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", "dev": true }, + "lodash._basedifference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basedifference/-/lodash._basedifference-3.0.3.tgz", + "integrity": "sha1-8sIEKWwqeOArOJCBtu3KyTPPYpw=", + "dev": true, + "requires": { + "lodash._baseindexof": "^3.0.0", + "lodash._cacheindexof": "^3.0.0", + "lodash._createcache": "^3.0.0" + } + }, + "lodash._baseflatten": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz", + "integrity": "sha1-B3D/gBMa9uNPO1EXlqe6UhTmX/c=", + "dev": true, + "requires": { + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash._basefor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", + "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=", + "dev": true + }, + "lodash._baseindexof": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", + "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", + "dev": true + }, "lodash._basetostring": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", @@ -15794,6 +48730,38 @@ "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", "dev": true }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", + "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", + "dev": true + }, + "lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "dev": true, + "requires": { + "lodash._bindcallback": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "lodash._createcache": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", + "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, "lodash._escapehtmlchar": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", @@ -15839,6 +48807,22 @@ "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", "dev": true }, + "lodash._pickbyarray": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash._pickbyarray/-/lodash._pickbyarray-3.0.2.tgz", + "integrity": "sha1-H4mNlgfrVgsOFnOEt3x8bRCKpMU=", + "dev": true + }, + "lodash._pickbycallback": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._pickbycallback/-/lodash._pickbycallback-3.0.0.tgz", + "integrity": "sha1-/2G5oBens699MObFPeKK+hm4dQo=", + "dev": true, + "requires": { + "lodash._basefor": "^3.0.0", + "lodash.keysin": "^3.0.0" + } + }, "lodash._reescape": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", @@ -15882,6 +48866,13 @@ "lodash._objecttypes": "~2.4.1" } }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true, + "optional": true + }, "lodash.clone": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", @@ -15894,6 +48885,12 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -15947,6 +48944,12 @@ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", "dev": true }, + "lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", + "dev": true + }, "lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", @@ -15959,6 +48962,12 @@ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", "dev": true }, + "lodash.istypedarray": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", + "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=", + "dev": true + }, "lodash.keys": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", @@ -15981,12 +48990,44 @@ } } }, + "lodash.keysin": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz", + "integrity": "sha1-IsRJPrvtsUJ5YqVLRFssinZ/tH8=", + "dev": true, + "requires": { + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.omit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-3.1.0.tgz", + "integrity": "sha1-iX/jguZBPZrJfGH3jtHgV6AK+fM=", + "dev": true, + "requires": { + "lodash._arraymap": "^3.0.0", + "lodash._basedifference": "^3.0.0", + "lodash._baseflatten": "^3.0.0", + "lodash._bindcallback": "^3.0.0", + "lodash._pickbyarray": "^3.0.0", + "lodash._pickbycallback": "^3.0.0", + "lodash.keysin": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, "lodash.pickby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", @@ -16005,12 +49046,6 @@ "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", "dev": true }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, "lodash.template": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", @@ -16048,6 +49083,22 @@ "lodash.escape": "~2.4.1" } }, + "lodash.toplainobject": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz", + "integrity": "sha1-KHkK2ULSk9eKpmOgfs9/UsoEGY0=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash.keysin": "^3.0.0" + } + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, "lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -16076,36 +49127,94 @@ "dev": true }, "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "chalk": "^2.4.2" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "log4js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", - "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", + "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", "dev": true, "requires": { - "date-format": "^2.0.0", + "date-format": "^3.0.0", "debug": "^4.1.1", - "flatted": "^2.0.0", + "flatted": "^2.0.1", "rfdc": "^1.1.4", - "streamroller": "^1.0.6" + "streamroller": "^2.2.4" }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -16115,9 +49224,9 @@ } }, "loglevel": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", - "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", + "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", "dev": true }, "loglevel-plugin-prefix": { @@ -16154,15 +49263,6 @@ "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", "dev": true }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -16198,37 +49298,23 @@ "es5-ext": "~0.10.2" } }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", "dev": true, + "optional": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } + "sourcemap-codec": "^1.4.4" } }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "make-error-cause": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz", - "integrity": "sha1-3wOI/NCzeBbf8KX7gQiTl3fcvJ0=", + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { - "make-error": "^1.2.0" + "semver": "^6.0.0" } }, "make-iterator": { @@ -16240,15 +49326,6 @@ "kind-of": "^6.0.2" } }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -16276,22 +49353,104 @@ "object-visit": "^1.0.0" } }, - "markdown-escapes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", - "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", - "dev": true + "markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + } + } + }, + "markdown-it-anchor": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", + "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", + "dev": true, + "requires": {} }, "markdown-table": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", - "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "requires": { + "repeat-string": "^1.0.0" + } + }, + "markdownlint": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.0.6.tgz", + "integrity": "sha1-8OPav/D4ZeGMrRy0f+80vLY4qG0=", + "dev": true, + "requires": { + "markdown-it": "^4.2.2" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "linkify-it": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-1.2.4.tgz", + "integrity": "sha1-B3NSbDF8j9E71TTuHRgP+Iq/iBo=", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "markdown-it": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-4.4.0.tgz", + "integrity": "sha1-PfNz2+pYepp/7z5WMRtokI91xBQ=", + "dev": true, + "requires": { + "argparse": "~1.0.2", + "entities": "~1.1.1", + "linkify-it": "~1.2.0", + "mdurl": "~1.0.0", + "uc.micro": "^1.0.0" + } + } + } + }, + "marked": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", + "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", "dev": true }, "marky": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.1.tgz", - "integrity": "sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.2.tgz", + "integrity": "sha512-k1dB2HNeaNyORco8ulVEhctyEGkKHb2YWAhDsxeFlW2nROIirsctBYzKwwS3Vza+sKTS1zO4Z+n9/+9WbGLIxQ==", "dev": true }, "matchdep": { @@ -16370,12 +49529,6 @@ "resolve-dir": "^1.0.1" } }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "is-glob": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", @@ -16455,22 +49608,105 @@ "safe-buffer": "^5.1.2" } }, - "mdast-util-compact": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz", - "integrity": "sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==", + "mdast-util-definitions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", + "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", "dev": true, "requires": { - "unist-util-visit": "^1.1.0" + "unist-util-visit": "^2.0.0" } }, - "mdast-util-definitions": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-1.2.5.tgz", - "integrity": "sha512-CJXEdoLfiISCDc2JB6QLb79pYfI6+GcIH+W2ox9nMc7od0Pz+bovcHsiq29xAQY6ayqe/9CsK2VzkSJdg1pFYA==", + "mdast-util-find-and-replace": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz", + "integrity": "sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==", + "dev": true, + "requires": { + "escape-string-regexp": "^4.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + } + } + }, + "mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "dependencies": { + "mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true + } + } + }, + "mdast-util-gfm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-0.1.2.tgz", + "integrity": "sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==", + "dev": true, + "requires": { + "mdast-util-gfm-autolink-literal": "^0.1.0", + "mdast-util-gfm-strikethrough": "^0.2.0", + "mdast-util-gfm-table": "^0.1.0", + "mdast-util-gfm-task-list-item": "^0.1.0", + "mdast-util-to-markdown": "^0.6.1" + } + }, + "mdast-util-gfm-autolink-literal": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.3.tgz", + "integrity": "sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==", + "dev": true, + "requires": { + "ccount": "^1.0.0", + "mdast-util-find-and-replace": "^1.1.0", + "micromark": "^2.11.3" + } + }, + "mdast-util-gfm-strikethrough": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz", + "integrity": "sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==", + "dev": true, + "requires": { + "mdast-util-to-markdown": "^0.6.0" + } + }, + "mdast-util-gfm-table": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.6.tgz", + "integrity": "sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==", + "dev": true, + "requires": { + "markdown-table": "^2.0.0", + "mdast-util-to-markdown": "~0.6.0" + } + }, + "mdast-util-gfm-task-list-item": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz", + "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==", "dev": true, "requires": { - "unist-util-visit": "^1.0.0" + "mdast-util-to-markdown": "~0.6.0" } }, "mdast-util-inject": { @@ -16483,22 +49719,41 @@ } }, "mdast-util-to-hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-3.0.4.tgz", - "integrity": "sha512-/eIbly2YmyVgpJNo+bFLLMCI1XgolO/Ffowhf+pHDq3X4/V6FntC9sGQCDLM147eTS+uSXv5dRzJyFn+o0tazA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz", + "integrity": "sha512-JoPBfJ3gBnHZ18icCwHR50orC9kNH81tiR1gs01D8Q5YpV6adHNO9nKNuFBCJQ941/32PT1a63UF/DitmS3amQ==", "dev": true, "requires": { - "collapse-white-space": "^1.0.0", - "detab": "^2.0.0", - "mdast-util-definitions": "^1.2.0", - "mdurl": "^1.0.1", - "trim": "0.0.1", - "trim-lines": "^1.0.0", - "unist-builder": "^1.0.1", - "unist-util-generated": "^1.1.0", + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "mdast-util-definitions": "^4.0.0", + "mdurl": "^1.0.0", + "unist-builder": "^2.0.0", + "unist-util-generated": "^1.0.0", "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.0", - "xtend": "^4.0.1" + "unist-util-visit": "^2.0.0" + } + }, + "mdast-util-to-markdown": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", + "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "longest-streak": "^2.0.0", + "mdast-util-to-string": "^2.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.0.0", + "zwitch": "^1.0.0" + }, + "dependencies": { + "mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true + } } }, "mdast-util-to-string": { @@ -16508,15 +49763,18 @@ "dev": true }, "mdast-util-toc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-3.1.0.tgz", - "integrity": "sha512-Za0hqL1PqWrvxGtA/3NH9D5nhGAUS9grMM4obEAz5+zsk1RIw/vWUchkaoDLNdrwk05A0CSC5eEXng36/1qE5w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-5.1.0.tgz", + "integrity": "sha512-csimbRIVkiqc+PpFeKDGQ/Ck2N4f9FYH3zzBMMJzcxoKL8m+cM0n94xXm0I9eaxHnKdY9n145SGTdyJC7i273g==", "dev": true, "requires": { + "@types/mdast": "^3.0.3", + "@types/unist": "^2.0.3", + "extend": "^3.0.2", "github-slugger": "^1.2.1", - "mdast-util-to-string": "^1.0.5", - "unist-util-is": "^2.1.2", - "unist-util-visit": "^1.1.0" + "mdast-util-to-string": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit": "^2.0.0" }, "dependencies": { "emoji-regex": { @@ -16534,10 +49792,10 @@ "emoji-regex": ">=6.0.0 <=6.1.1" } }, - "unist-util-is": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.3.tgz", - "integrity": "sha512-4WbQX2iwfr/+PfM4U3zd2VNXY+dWtZsN1fLnWEi2QQXA4qyDYAZcDMfXUX0Cu6XZUHHAO9q4nyxxLT4Awk1qUA==", + "mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", "dev": true } } @@ -16571,19 +49829,27 @@ } }, "memoizee": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "dependencies": { + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + } } }, "memory-fs": { @@ -16610,48 +49876,191 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } } } }, - "memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", - "dev": true - }, "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", "dev": true, "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" }, "dependencies": { "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + } + }, + "hosted-git-info": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "map-obj": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", + "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", + "dev": true + }, + "normalize-package-data": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", + "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" } + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, @@ -16688,14 +50097,97 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, + "micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "dev": true, + "requires": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "micromark-extension-gfm": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz", + "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==", + "dev": true, + "requires": { + "micromark": "~2.11.0", + "micromark-extension-gfm-autolink-literal": "~0.5.0", + "micromark-extension-gfm-strikethrough": "~0.6.5", + "micromark-extension-gfm-table": "~0.4.0", + "micromark-extension-gfm-tagfilter": "~0.3.0", + "micromark-extension-gfm-task-list-item": "~0.3.0" + } + }, + "micromark-extension-gfm-autolink-literal": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz", + "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==", + "dev": true, + "requires": { + "micromark": "~2.11.3" + } + }, + "micromark-extension-gfm-strikethrough": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz", + "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==", + "dev": true, + "requires": { + "micromark": "~2.11.0" + } + }, + "micromark-extension-gfm-table": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz", + "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==", + "dev": true, + "requires": { + "micromark": "~2.11.0" + } + }, + "micromark-extension-gfm-tagfilter": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz", + "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==", + "dev": true + }, + "micromark-extension-gfm-task-list-item": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz", + "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==", + "dev": true, + "requires": { + "micromark": "~2.11.0" + } + }, "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "dev": true, "requires": { "braces": "^3.0.1", - "picomatch": "^2.0.5" + "picomatch": "^2.2.3" } }, "miller-rabin": { @@ -16709,9 +50201,9 @@ }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -16722,16 +50214,16 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", "requires": { - "mime-db": "1.44.0" + "mime-db": "1.48.0" } }, "mimic-fn": { @@ -16746,6 +50238,12 @@ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -16773,6 +50271,25 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "dependencies": { + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + } + } + }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -16825,6 +50342,12 @@ "supports-color": "5.4.0" }, "dependencies": { + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -16886,22 +50409,248 @@ } } }, + "mocha-phantomjs": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/mocha-phantomjs/-/mocha-phantomjs-3.6.0.tgz", + "integrity": "sha1-mxN/yMiRuPpd1ln3OOyVRYt0+5c=", + "dev": true, + "requires": { + "commander": "~2.0.0", + "mocha": "~1.20.1" + }, + "dependencies": { + "commander": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=", + "dev": true + }, + "diff": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=", + "dev": true + }, + "glob": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "dev": true, + "requires": { + "graceful-fs": "~2.0.0", + "inherits": "2", + "minimatch": "~0.2.11" + } + }, + "graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", + "dev": true + }, + "growl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true + }, + "mocha": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.20.1.tgz", + "integrity": "sha1-80ODLZ/gx9l8ZPxwRI9RNt+f7Vs=", + "dev": true, + "requires": { + "commander": "2.0.0", + "debug": "*", + "diff": "1.0.7", + "glob": "3.2.3", + "growl": "1.7.x", + "jade": "0.26.3", + "mkdirp": "0.3.5" + } + } + } + }, + "modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true + }, + "module-deps": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-3.9.1.tgz", + "integrity": "sha1-6nXK+RmQkNJbDVUStaysuW5/h/M=", + "dev": true, + "requires": { + "browser-resolve": "^1.7.0", + "concat-stream": "~1.4.5", + "defined": "^1.0.0", + "detective": "^4.0.0", + "duplexer2": "0.0.2", + "inherits": "^2.0.1", + "JSONStream": "^1.0.3", + "parents": "^1.0.0", + "readable-stream": "^1.1.13", + "resolve": "^1.1.3", + "stream-combiner2": "~1.0.0", + "subarg": "^1.0.0", + "through2": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "detective": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", + "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", + "dev": true, + "requires": { + "acorn": "^5.2.1", + "defined": "^1.0.0" + } + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "stream-combiner2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.0.2.tgz", + "integrity": "sha1-unKmtQy/q/qVD8i8h2BL0B62BnE=", + "dev": true, + "requires": { + "duplexer2": "~0.0.2", + "through2": "~0.5.1" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + } + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "requires": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } + } + }, "module-deps-sortable": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/module-deps-sortable/-/module-deps-sortable-4.0.6.tgz", - "integrity": "sha1-ElGkuixEqS32mJvQKdoSGk8hCbA=", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/module-deps-sortable/-/module-deps-sortable-5.0.3.tgz", + "integrity": "sha512-eiyIZj/A0dj1o4ywXWqicazUL3l0HP3TydUR6xF0X3xh3LGBMLqW8a9aFe6MuNH4mxNMk53QKBHM6LOPR8kSgw==", "dev": true, "requires": { - "JSONStream": "^1.0.3", "browser-resolve": "^1.7.0", + "cached-path-relative": "^1.0.0", "concat-stream": "~1.5.0", "defined": "^1.0.0", - "detective": "^4.0.0", + "detective": "^5.2.0", "duplexer2": "^0.1.2", "inherits": "^2.0.1", - "parents": "^1.0.0", + "JSONStream": "^1.0.3", + "konan": "^2.1.1", "readable-stream": "^2.0.2", "resolve": "^1.1.3", + "standard-version": "^9.0.0", "stream-combiner2": "^1.1.1", "subarg": "^1.0.0", "through2": "^2.0.0", @@ -16978,9 +50727,25 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } } } }, + "module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", + "dev": true + }, "morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -17064,12 +50829,18 @@ "dev": true }, "nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", "dev": true, "optional": true }, + "nanoid": { + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", + "dev": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -17089,6 +50860,13 @@ "to-regex": "^3.0.1" } }, + "natives": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", + "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==", + "dev": true, + "optional": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -17112,6 +50890,15 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "nested-error-stacks": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz", + "integrity": "sha1-GfYZWRUZ8JZ2mlupqG5u7sgjw88=", + "dev": true, + "requires": { + "inherits": "~2.0.1" + } + }, "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", @@ -17173,10 +50960,10 @@ } } }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", "dev": true }, "node-libs-browser": { @@ -17240,43 +51027,74 @@ "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" + }, + "dependencies": { + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } } } }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "node-releases": { + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", "dev": true }, - "node-notifier": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", - "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", + "dev": true + }, + "nomnom": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", + "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=", "dev": true, "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" + "chalk": "~0.4.0", + "underscore": "~1.6.0" }, "dependencies": { - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true + }, + "chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true, + "requires": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + } + }, + "strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true + }, + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", "dev": true } } }, - "node-releases": { - "version": "1.1.60", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", - "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", - "dev": true - }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -17286,6 +51104,12 @@ "abbrev": "1" } }, + "nopt-usage": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/nopt-usage/-/nopt-usage-0.1.0.tgz", + "integrity": "sha1-sYuMGD4YEEfKnmO3zefPxwLMpXk=", + "dev": true + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -17296,6 +51120,14 @@ "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "normalize-path": { @@ -17305,9 +51137,9 @@ "dev": true }, "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", "dev": true }, "now-and-later": { @@ -17319,88 +51151,35 @@ "once": "^1.3.2" } }, - "npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "npm-license": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/npm-license/-/npm-license-0.2.3.tgz", + "integrity": "sha1-49ARePz4zcyWg53E06Z9ZxE/vYs=", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" + "mkdirp": "~0.5.0", + "nopt": "~3.0.1", + "nopt-usage": "^0.1.0", + "package-license": "~0.1.1", + "pkginfo": "^0.3.0", + "read-installed": "~3.1.3", + "treeify": "~1.0.1", + "underscore": "~1.4.4" }, "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "minimist": "^1.2.5" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "underscore": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", + "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=", "dev": true } } @@ -17412,6 +51191,71 @@ "dev": true, "requires": { "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + } + } + }, + "npmconf": { + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/npmconf/-/npmconf-0.0.24.tgz", + "integrity": "sha1-t4h1sIjMw8Cvo+zrPOMkSxtSOQw=", + "dev": true, + "requires": { + "config-chain": "~1.1.1", + "inherits": "~1.0.0", + "ini": "~1.1.0", + "mkdirp": "~0.3.3", + "nopt": "2", + "once": "~1.1.1", + "osenv": "0.0.3", + "semver": "~1.1.0" + }, + "dependencies": { + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "ini": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.1.0.tgz", + "integrity": "sha1-ToCMLOFExsF4iRjgNNZ5e8bPYoE=", + "dev": true + }, + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true + }, + "nopt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.2.1.tgz", + "integrity": "sha1-KqCbfRdoSHs7ianFqlIzW/8Lrqc=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "once": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/once/-/once-1.1.1.tgz", + "integrity": "sha1-nbV0kzzLCMOnYU0VQDLAnqbzOec=", + "dev": true + }, + "semver": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-1.1.4.tgz", + "integrity": "sha1-LlpOcrqwNHLMl/cnU7RQiRLvVUA=", + "dev": true + } } }, "null-check": { @@ -17426,12 +51270,6 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -17444,12 +51282,6 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "dev": true - }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -17470,12 +51302,6 @@ "is-descriptor": "^0.1.0" } }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -17488,19 +51314,19 @@ } }, "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", + "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", "dev": true }, "object-is": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", - "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, "object-keys": { @@ -17519,15 +51345,15 @@ } }, "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" } }, "object.defaults": { @@ -17542,16 +51368,6 @@ "isobject": "^3.0.0" } }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, "object.map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", @@ -17603,15 +51419,14 @@ } }, "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", + "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "es-abstract": "^1.18.2" } }, "on-finished": { @@ -17638,18 +51453,24 @@ } }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { "mimic-fn": "^2.1.0" } }, + "open": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/open/-/open-0.0.3.tgz", + "integrity": "sha1-+jd/T/MIIS2SqbjmOVJAhUZGpxM=", + "dev": true + }, "opener": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", - "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", "dev": true }, "opn": { @@ -17685,28 +51506,96 @@ "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, "ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -17730,6 +51619,15 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -17739,55 +51637,13 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - } + "lcid": "^1.0.0" } }, "os-tmpdir": { @@ -17796,20 +51652,17 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, - "p-cancelable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", - "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==", + "osenv": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.0.3.tgz", + "integrity": "sha1-zWrY3bKQkVrZ4idlV2Al1BHynLY=", "dev": true }, - "p-each-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", - "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", - "dev": true, - "requires": { - "p-reduce": "^1.0.0" - } + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true }, "p-finally": { "version": "1.0.0", @@ -17817,12 +51670,6 @@ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, - "p-is-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", - "dev": true - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -17841,33 +51688,33 @@ "p-limit": "^2.2.0" } }, - "p-reduce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", - "dev": true - }, - "p-timeout": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", - "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", - "dev": true, - "requires": { - "p-finally": "^1.0.0" - } - }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "package-license": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/package-license/-/package-license-0.1.2.tgz", + "integrity": "sha1-9cRJp6UPrfSiI0HilJCZ+RpCAos=", + "dev": true + }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parents": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", @@ -17878,200 +51725,22 @@ } }, "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", "dev": true, "requires": { - "asn1.js": "^4.0.0", + "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3", "safe-buffer": "^5.1.1" } }, - "parse-domain": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/parse-domain/-/parse-domain-2.3.4.tgz", - "integrity": "sha512-LlFJJVTry4DD3Xa76CsVNP6MIu3JZ8GXd5HEEp38KSDGBCVsnccagAJ5YLy7uEEabvwtauQEQPcvXWgUGkJbMA==", - "dev": true, - "requires": { - "got": "^8.3.2", - "jest": "^24.9.0", - "mkdirp": "^0.5.1", - "npm-run-all": "^4.1.5" - }, - "dependencies": { - "@sindresorhus/is": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", - "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", - "dev": true - }, - "cacheable-request": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", - "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", - "dev": true, - "requires": { - "clone-response": "1.0.2", - "get-stream": "3.0.0", - "http-cache-semantics": "3.8.1", - "keyv": "3.0.0", - "lowercase-keys": "1.0.0", - "normalize-url": "2.0.1", - "responselike": "1.0.2" - }, - "dependencies": { - "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", - "dev": true - } - } - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "got": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", - "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.7.0", - "cacheable-request": "^2.1.1", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "into-stream": "^3.1.0", - "is-retry-allowed": "^1.1.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "mimic-response": "^1.0.0", - "p-cancelable": "^0.4.0", - "p-timeout": "^2.0.1", - "pify": "^3.0.0", - "safe-buffer": "^5.1.1", - "timed-out": "^4.0.1", - "url-parse-lax": "^3.0.0", - "url-to-options": "^1.0.1" - } - }, - "http-cache-semantics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", - "dev": true - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, - "keyv": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", - "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "normalize-url": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", - "dev": true, - "requires": { - "prepend-http": "^2.0.0", - "query-string": "^5.0.1", - "sort-keys": "^2.0.0" - } - }, - "p-cancelable": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", - "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "dev": true, - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, - "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", - "dev": true, - "requires": { - "is-plain-obj": "^1.0.0" - } - } - } - }, "parse-entities": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", - "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", "dev": true, "requires": { "character-entities": "^1.0.0", @@ -18093,14 +51762,11 @@ "path-root": "^0.1.1" } }, - "parse-git-config": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/parse-git-config/-/parse-git-config-0.2.0.tgz", - "integrity": "sha1-Jygz/dFf6hRvt10zbSNrljtv9wY=", - "dev": true, - "requires": { - "ini": "^1.3.3" - } + "parse-github-repo-url": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", + "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=", + "dev": true }, "parse-glob": { "version": "3.0.4", @@ -18159,63 +51825,46 @@ "dev": true }, "parse-path": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-3.0.4.tgz", - "integrity": "sha512-wP70vtwv2DyrM2YoA7ZHVv4zIXa4P7dGgHlj+VwyXNDduLLVJ7NMY1zsFxjUUJ3DAwJLupGb1H5gMDDiNlJaxw==", - "dev": true, - "requires": { - "is-ssh": "^1.3.0", - "protocols": "^1.4.0" - } - }, - "parse-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-3.0.2.tgz", - "integrity": "sha1-YCeHpwY6eV1yuGcxl1BecvYGEL4=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.3.tgz", + "integrity": "sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA==", "dev": true, "requires": { "is-ssh": "^1.3.0", - "normalize-url": "^1.9.1", - "parse-path": "^3.0.1", - "protocols": "^1.4.0" + "protocols": "^1.4.0", + "qs": "^6.9.4", + "query-string": "^6.13.8" }, "dependencies": { - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", "dev": true, "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" + "side-channel": "^1.0.4" } } } }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "parse-url": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.5.tgz", + "integrity": "sha512-AwfVhXaQrNNI6UPUJq/GJN2qoY0L9gPgxhh9VbDP0bfBAJWaC/Zh8hjQ58YKTi4AagOT70fpadkYSKPo+eFb1w==", "dev": true, "requires": { - "better-assert": "~1.0.0" + "is-ssh": "^1.3.0", + "normalize-url": "4.5.0", + "parse-path": "^4.0.0", + "protocols": "^1.4.0" + }, + "dependencies": { + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + } } }, "parseurl": { @@ -18260,15 +51909,15 @@ "dev": true }, "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "path-platform": { @@ -18309,9 +51958,9 @@ } }, "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, "pause-stream": { @@ -18324,9 +51973,9 @@ } }, "pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", "dev": true, "requires": { "create-hash": "^1.1.2", @@ -18354,16 +52003,181 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true + "phantomjs": { + "version": "1.9.7-15", + "resolved": "https://registry.npmjs.org/phantomjs/-/phantomjs-1.9.7-15.tgz", + "integrity": "sha1-Czp85jBIaoO+kf9Ogy7uIOlxEVs=", + "dev": true, + "requires": { + "adm-zip": "0.2.1", + "kew": "~0.1.7", + "mkdirp": "0.3.5", + "ncp": "0.4.2", + "npmconf": "0.0.24", + "progress": "^1.1.5", + "request": "2.36.0", + "request-progress": "^0.3.1", + "rimraf": "~2.2.2", + "which": "~1.0.5" + }, + "dependencies": { + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true, + "optional": true + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "optional": true, + "requires": { + "delayed-stream": "0.0.5" + } + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true, + "optional": true + }, + "forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=", + "dev": true + }, + "form-data": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", + "dev": true, + "optional": true, + "requires": { + "async": "~0.9.0", + "combined-stream": "~0.0.4", + "mime": "~1.2.11" + } + }, + "hawk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.0.0.tgz", + "integrity": "sha1-uQuxaYByhUEdp//LjdJZhQLTtS0=", + "dev": true, + "optional": true, + "requires": { + "boom": "0.4.x", + "cryptiles": "0.2.x", + "hoek": "0.9.x", + "sntp": "0.2.x" + } + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "dev": true, + "optional": true, + "requires": { + "asn1": "0.1.11", + "assert-plus": "^0.1.5", + "ctype": "0.5.3" + } + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true + }, + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true + }, + "oauth-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", + "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", + "dev": true, + "optional": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "qs": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz", + "integrity": "sha1-bgFQmP9RlouKPIGQAdXyyJvEsQc=", + "dev": true + }, + "request": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.36.0.tgz", + "integrity": "sha1-KMbAQmLHuf/dIbklU3RRfubZQ/U=", + "dev": true, + "requires": { + "aws-sign2": "~0.5.0", + "forever-agent": "~0.5.0", + "form-data": "~0.1.0", + "hawk": "~1.0.0", + "http-signature": "~0.10.0", + "json-stringify-safe": "~5.0.0", + "mime": "~1.2.9", + "node-uuid": "~1.4.0", + "oauth-sign": "~0.3.0", + "qs": "~0.6.0", + "tough-cookie": ">=0.12.0", + "tunnel-agent": "~0.4.0" + } + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true, + "optional": true + }, + "which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true + } + } }, - "pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "dev": true }, "pify": { @@ -18387,15 +52201,6 @@ "pinkie": "^2.0.0" } }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -18405,6 +52210,72 @@ "find-up": "^4.0.0" } }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "pkginfo": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", + "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=", + "dev": true + }, "plugin-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", @@ -18434,11 +52305,22 @@ "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true + "portfinder": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-0.2.1.tgz", + "integrity": "sha1-srmwFk+eF/o6nH2yME0KdRQMca0=", + "dev": true, + "requires": { + "mkdirp": "0.0.x" + }, + "dependencies": { + "mkdirp": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.7.tgz", + "integrity": "sha1-2JtPDkw+XlylQjWTFnXglP4aUHI=", + "dev": true + } + } }, "posix-character-classes": { "version": "0.1.1", @@ -18446,10 +52328,97 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "postcss": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz", + "integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==", + "dev": true, + "optional": true, + "requires": { + "colorette": "^1.2.2", + "nanoid": "^3.1.23", + "source-map-js": "^0.6.2" + } + }, + "postcss-modules": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.1.3.tgz", + "integrity": "sha512-dBT39hrXe4OAVYJe/2ZuIZ9BzYhOe7t+IhedYeQ2OxKwDpAGlkEN/fR0fGnrbx4BvgbMReRX4hCubYK9cE/pJQ==", + "dev": true, + "optional": true, + "requires": { + "generic-names": "^2.0.1", + "icss-replace-symbols": "^1.1.0", + "lodash.camelcase": "^4.3.0", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "string-hash": "^1.1.1" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "optional": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "optional": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "optional": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "optional": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", + "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", + "dev": true, + "optional": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true, + "optional": true + }, "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "prepend-http": { @@ -18457,48 +52426,29 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-format": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.1.0.tgz", - "integrity": "sha512-GmeO1PEYdM+non4BKCj+XsPJjFOJIPnsLewqhDVoqY1xo0yNmDas7tC2XwpMrRAHR3MaE2hPo37deX5OisJ2Wg==", - "dev": true, - "requires": { - "@jest/types": "^26.1.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-format": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.2.tgz", + "integrity": "sha512-mXKbbBPnYTG7Yra9qFBtqj+IXcsvxsvOBco3QHxtxTl+hHKq6QdzMZ+q0CtL4ORHZgwGImRr2XZUX2EWzORxig==", + "dev": true, + "requires": { + "@jest/types": "^27.0.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true } } @@ -18510,18 +52460,18 @@ "dev": true }, "pretty-ms": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.0.tgz", - "integrity": "sha512-J3aPWiC5e9ZeZFuSeBraGxSkGMOvulSWsxDByOcbD1Pr75YL3LSNIKIb52WXbCLE1sS5s4inBBbryjF4Y05Ceg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", "dev": true, "requires": { "parse-ms": "^2.1.0" } }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", "dev": true }, "process": { @@ -18542,47 +52492,33 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "promise.allsettled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", - "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", - "dev": true, - "requires": { - "array.prototype.map": "^1.0.1", - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "iterate-value": "^1.0.0" - } - }, - "prompts": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", - "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==", + "property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", "dev": true, "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.4" + "xtend": "^4.0.0" } }, - "property-information": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-3.2.0.tgz", - "integrity": "sha1-/RSDyPusYYCPX+NZ52k6H0ilgzE=", + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", "dev": true }, "protocols": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.7.tgz", - "integrity": "sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz", + "integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==", "dev": true }, "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "requires": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, @@ -18592,6 +52528,25 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, + "proxyquire": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-1.8.0.tgz", + "integrity": "sha1-AtUUpb7ZhvBMuyCTrxZ0FTX3ntw=", + "dev": true, + "requires": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.0", + "resolve": "~1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -18634,9 +52589,9 @@ }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -18681,40 +52636,59 @@ "dev": true }, "puppeteer-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.2.1.tgz", - "integrity": "sha512-gLjEOrzwgcnwRH+sm4hS1TBqe2/DN248nRb2hYB7+lZ9kCuLuACNvuzlXILlPAznU3Ob+mEvVEBDcLuFa0zq3g==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-9.1.1.tgz", + "integrity": "sha512-zbedbitVIGhmgz0nt7eIdLsnaoVZSlNJfBivqm2w67T8LR2bU1dvnruDZ8nQO0zn++Iet7zHbAOdnuS5+H2E7A==", "dev": true, "requires": { "debug": "^4.1.0", - "devtools-protocol": "0.0.781568", + "devtools-protocol": "0.0.869402", "extract-zip": "^2.0.0", - "https-proxy-agent": "^4.0.0", - "mime": "^2.0.3", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", "pkg-dir": "^4.2.0", "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", + "proxy-from-env": "^1.1.0", "rimraf": "^3.0.2", "tar-fs": "^2.0.0", "unbzip2-stream": "^1.3.3", "ws": "^7.2.3" }, "dependencies": { + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "devtools-protocol": { + "version": "0.0.869402", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", + "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", "dev": true }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -18749,14 +52723,22 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "query-selector-shadow-dom": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.0.tgz", + "integrity": "sha512-bK0/0cCI+R8ZmOF1QjT7HupDUYCxbf/9TJgAmSXQxZpftXmTAeil9DRoCnTDkWbvOyZzhcMBwKpptWcdkGFIMg==", + "dev": true + }, "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", + "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", "dev": true, "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" } }, "querystring": { @@ -18838,11 +52820,134 @@ } }, "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "read-all-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", + "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "read-installed": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-3.1.5.tgz", + "integrity": "sha1-SuNgga/T4iBNwuJ5gHqqUsMMjAw=", + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "2 || 3", + "read-package-json": "1", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + }, + "dependencies": { + "graceful-fs": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", + "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", + "dev": true, + "optional": true, + "requires": { + "natives": "^1.1.3" + } + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + } + } + }, + "read-package-json": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-1.3.3.tgz", + "integrity": "sha1-73nf2kbhZTdu6KV++/7dTRsCm6Q=", + "dev": true, + "requires": { + "glob": "^5.0.3", + "graceful-fs": "2 || 3", + "json-parse-helpfulerror": "^1.0.2", + "normalize-package-data": "^1.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", + "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", + "dev": true, + "optional": true, + "requires": { + "natives": "^1.1.3" + } + }, + "normalize-package-data": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-1.0.3.tgz", + "integrity": "sha1-i+lVuJB6+XXxpFhOqLubQUkjEvU=", + "dev": true, + "requires": { + "github-url-from-git": "^1.3.0", + "github-url-from-username-repo": "^1.0.0", + "semver": "2 || 3 || 4" + } + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + } + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -18896,22 +53001,102 @@ "util-deprecate": "^1.0.1" } }, + "readable-wrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readable-wrap/-/readable-wrap-1.0.0.tgz", + "integrity": "sha1-O1ohHGMeEjA6VJkcgGwX564ga/8=", + "dev": true, + "requires": { + "readable-stream": "^1.1.13-1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { "picomatch": "^2.2.1" } }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "readline2": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz", + "integrity": "sha1-mUQ7pug7gw7zBRv9fcJBqCco1Wg=", "dev": true, "requires": { - "util.promisify": "^1.0.0" + "mute-stream": "0.0.4", + "strip-ansi": "^2.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", + "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=", + "dev": true + }, + "mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha1-qSGZYKbV1dBGWXruUSUsZlX3F34=", + "dev": true + }, + "strip-ansi": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", + "integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=", + "dev": true, + "requires": { + "ansi-regex": "^1.0.0" + } + } } }, "rechoir": { @@ -18933,30 +53118,27 @@ } }, "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" }, "dependencies": { "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true } } }, "regenerate": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", - "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", "dev": true }, "regenerate-unicode-properties": { @@ -19002,25 +53184,25 @@ } }, "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", + "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", "dev": true, "requires": { "regenerate": "^1.4.0", @@ -19038,9 +53220,9 @@ "dev": true }, "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", + "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -19055,110 +53237,72 @@ } }, "remark": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-9.0.0.tgz", - "integrity": "sha512-amw8rGdD5lHbMEakiEsllmkdBP+/KpjW/PRK6NSGPZKCQowh0BT4IWXDAkRMyG3SB9dKPXWMviFjNusXzXNn3A==", - "dev": true, - "requires": { - "remark-parse": "^5.0.0", - "remark-stringify": "^5.0.0", - "unified": "^6.0.0" - } - }, - "remark-html": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-7.0.0.tgz", - "integrity": "sha512-jqRzkZXCkM12gIY2ibMLTW41m7rfanliMTVQCFTezHJFsbH00YaTox/BX4gU+f/zCdzfhFJONtebFByvpMv37w==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", + "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==", "dev": true, "requires": { - "hast-util-sanitize": "^1.0.0", - "hast-util-to-html": "^3.0.0", - "mdast-util-to-hast": "^3.0.0", - "xtend": "^4.0.1" + "remark-parse": "^9.0.0", + "remark-stringify": "^9.0.0", + "unified": "^9.1.0" } }, - "remark-parse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", - "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", + "remark-gfm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-1.0.0.tgz", + "integrity": "sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==", "dev": true, "requires": { - "collapse-white-space": "^1.0.2", - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "is-word-character": "^1.0.0", - "markdown-escapes": "^1.0.0", - "parse-entities": "^1.1.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "trim": "0.0.1", - "trim-trailing-lines": "^1.0.0", - "unherit": "^1.0.4", - "unist-util-remove-position": "^1.0.0", - "vfile-location": "^2.0.0", - "xtend": "^4.0.1" + "mdast-util-gfm": "^0.1.0", + "micromark-extension-gfm": "^0.3.0" } }, - "remark-reference-links": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-4.0.4.tgz", - "integrity": "sha512-+2X8hwSQqxG4tvjYZNrTcEC+bXp8shQvwRGG6J/rnFTvBoU4G0BBviZoqKGZizLh/DG+0gSYhiDDWCqyxXW1iQ==", + "remark-html": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-13.0.1.tgz", + "integrity": "sha512-K5KQCXWVz+harnyC+UVM/J9eJWCgjYRqFeZoZf2NgP0iFbuuw/RgMZv3MA34b/OEpGnstl3oiOUtZzD3tJ+CBw==", "dev": true, "requires": { - "unist-util-visit": "^1.0.0" + "hast-util-sanitize": "^3.0.0", + "hast-util-to-html": "^7.0.0", + "mdast-util-to-hast": "^10.0.0" } }, - "remark-slug": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/remark-slug/-/remark-slug-5.1.2.tgz", - "integrity": "sha512-DWX+Kd9iKycqyD+/B+gEFO3jjnt7Yg1O05lygYSNTe5i5PIxxxPjp5qPBDxPIzp5wreF7+1ROCwRgjEcqmzr3A==", + "remark-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", + "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", "dev": true, "requires": { - "github-slugger": "^1.0.0", - "mdast-util-to-string": "^1.0.0", - "unist-util-visit": "^1.0.0" + "mdast-util-from-markdown": "^0.8.0" } }, - "remark-stringify": { + "remark-reference-links": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-5.0.0.tgz", - "integrity": "sha512-Ws5MdA69ftqQ/yhRF9XhVV29mhxbfGhbz0Rx5bQH+oJcNhhSM6nCu1EpLod+DjrFGrU0BMPs+czVmJZU7xiS7w==", + "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-5.0.0.tgz", + "integrity": "sha512-oSIo6lfDyG/1yYl2jPZNXmD9dgyPxp07mSd7snJagVMsDU6NRlD8i54MwHWUgMoOHTs8lIKPkwaUok/tbr5syQ==", "dev": true, "requires": { - "ccount": "^1.0.0", - "is-alphanumeric": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "longest-streak": "^2.0.1", - "markdown-escapes": "^1.0.0", - "markdown-table": "^1.1.0", - "mdast-util-compact": "^1.0.0", - "parse-entities": "^1.0.2", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "stringify-entities": "^1.0.1", - "unherit": "^1.0.4", - "xtend": "^4.0.1" + "unist-util-visit": "^2.0.0" } }, - "remark-toc": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-5.1.1.tgz", - "integrity": "sha512-vCPW4YOsm2CfyuScdktM9KDnJXVHJsd/ZeRtst+dnBU3B3KKvt8bc+bs5syJjyptAHfqo7H+5Uhz+2blWBfwow==", + "remark-stringify": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", + "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", "dev": true, "requires": { - "mdast-util-toc": "^3.0.0", - "remark-slug": "^5.0.0" + "mdast-util-to-markdown": "^0.6.0" } }, - "remote-origin-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/remote-origin-url/-/remote-origin-url-0.4.0.tgz", - "integrity": "sha1-TT4pAvNOLTfRwmPYdxC3frQIajA=", + "remark-toc": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-7.2.0.tgz", + "integrity": "sha512-ppHepvpbg7j5kPFmU5rzDC4k2GTcPDvWcxXyr/7BZzO1cBSPk0stKtEJdsgAyw2WHKPGxadcHIZRjb2/sHxjkg==", "dev": true, "requires": { - "parse-git-config": "^0.2.0" + "@types/unist": "^2.0.3", + "mdast-util-toc": "^5.0.0" } }, "remove-bom-buffer": { @@ -19169,14 +53313,6 @@ "requires": { "is-buffer": "^1.1.5", "is-utf8": "^0.2.1" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - } } }, "remove-bom-stream": { @@ -19188,6 +53324,42 @@ "remove-bom-buffer": "^3.0.0", "safe-buffer": "^5.1.0", "through2": "^2.0.3" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, "remove-trailing-separator": { @@ -19197,9 +53369,9 @@ "dev": true }, "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", "dev": true }, "repeat-string": { @@ -19218,9 +53390,9 @@ } }, "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", "dev": true }, "replace-homedir": { @@ -19259,6 +53431,15 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -19304,24 +53485,13 @@ } } }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "request-progress": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-0.3.1.tgz", + "integrity": "sha1-ByHBBdipasayzossia4tXs/Pazo=", "dev": true, "requires": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" + "throttleit": "~0.0.2" } }, "require-directory": { @@ -19330,6 +53500,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -19360,38 +53536,31 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "requizzle": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dev": true, "requires": { + "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } }, "resolve-alpn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", - "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.1.2.tgz", + "integrity": "sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA==", "dev": true }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - } - } - }, "resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -19423,6 +53592,12 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, + "response-stream": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/response-stream/-/response-stream-0.0.0.tgz", + "integrity": "sha1-2ksXzHaEyYyWK+tNlfZoyNytCdU=", + "dev": true + }, "responselike": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", @@ -19433,9 +53608,9 @@ } }, "resq": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resq/-/resq-1.7.1.tgz", - "integrity": "sha512-09u9Q5SAuJfAW5UoVAmvRtLvCOMaKP+djiixTXsZvPaojGKhuvc0Nfvp84U1rIfopJWEOXi5ywpCFwCk7mj8Xw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.10.0.tgz", + "integrity": "sha512-hCUd0xMalqtPDz4jXIqs0M5Wnv/LZXN8h7unFOo4/nvExT9dDPbhwd3udRxLlp0HgBnHcV009UlduE9NZi7A6w==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1" @@ -19465,16 +53640,40 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "rewire": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-2.5.2.tgz", + "integrity": "sha1-ZCfee3/u+n02QBUH62SlOFvFjcc=", + "dev": true + }, "rfdc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", - "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", "dev": true }, + "rfile": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rfile/-/rfile-1.0.0.tgz", + "integrity": "sha1-WXCM+Qyh50xUw8/Fw2/bmBBDUmE=", + "dev": true, + "requires": { + "callsite": "~1.0.0", + "resolve": "~0.3.0" + }, + "dependencies": { + "resolve": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.3.1.tgz", + "integrity": "sha1-NMY0R8ZkxwWY0cmxJvxDsqJDEKQ=", + "dev": true + } + } + }, "rgb2hex": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.0.tgz", - "integrity": "sha512-cHdNTwmTMPu/TpP1bJfdApd6MbD+Kzi4GNnM6h35mdFChhQPSi9cAI8J7DMn5kQDKX8NuBaQXAyo360Oa7tOEA==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.5.tgz", + "integrity": "sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==", "dev": true }, "right-align": { @@ -19491,255 +53690,148 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", "dev": true, - "requires": { - "glob": "^7.0.5" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true - }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, - "requires": { - "rx-lite": "*" - } - }, - "rxjs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", - "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-json-parse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", - "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", - "dev": true - }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, - "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "glob": "^7.0.5" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "ruglify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ruglify/-/ruglify-1.0.0.tgz", + "integrity": "sha1-3Ikw4qlUSidDAcyZcldMDQmGtnU=", + "dev": true, + "requires": { + "rfile": "~1.0", + "uglify-js": "~2.2" + }, + "dependencies": { + "optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "wordwrap": "~0.0.2" } }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "amdefine": ">=0.0.4" } }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "uglify-js": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", + "integrity": "sha1-puAqcNg5eSuXgEiLe4sYTAlcmcc=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "optimist": "~0.3.5", + "source-map": "~0.1.7" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true } } }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "rx": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz", + "integrity": "sha1-Ia3H2A8CACr1Da6X/Z2/JIdV9WY=", + "dev": true + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "*" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", "dev": true }, "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" }, "dependencies": { "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -19750,10 +53842,21 @@ } } }, + "script-injector": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/script-injector/-/script-injector-0.1.7.tgz", + "integrity": "sha1-ttzO4jcpezev9XWvl/p5FSWo194=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "through": "~2.3.4", + "trumpet": "~1.6.3" + } + }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "semver-greatest-satisfied-range": { @@ -19793,27 +53896,30 @@ } }, "serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", "dev": true, "requires": { - "type-fest": "^0.13.1" + "type-fest": "^0.20.2" }, "dependencies": { "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } } }, "serialize-javascript": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz", - "integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==", - "dev": true + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } }, "serve-index": { "version": "1.9.1", @@ -19911,31 +54017,47 @@ "safe-buffer": "^5.0.1" } }, + "shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", + "dev": true + }, + "shasum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true, + "requires": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-0.0.1.tgz", + "integrity": "sha1-GkEZbzwDM8SCMjWT1ohuzxU92YY=", "dev": true }, "shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "dev": true, "requires": { "glob": "^7.0.0", @@ -19943,22 +54065,40 @@ "rechoir": "^0.6.2" } }, - "shellwords": { + "shelljs-nodecli": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true + "resolved": "https://registry.npmjs.org/shelljs-nodecli/-/shelljs-nodecli-0.1.1.tgz", + "integrity": "sha1-8b61VjdUxHXQb30IM+D2B15GLTs=", + "dev": true, + "requires": { + "shelljs": "~0.2" + }, + "dependencies": { + "shelljs": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.2.6.tgz", + "integrity": "sha1-kEktcv/MgVmXa6umL7D2iE8MM3g=", + "dev": true + } + } }, "side-channel": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", - "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dev": true, "requires": { - "es-abstract": "^1.17.0-next.1", - "object-inspect": "^1.7.0" + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" } }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -19988,12 +54128,6 @@ } } }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -20001,22 +54135,48 @@ "dev": true }, "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true } } }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -20113,12 +54273,6 @@ "kind-of": "^3.2.0" }, "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -20130,119 +54284,84 @@ } } }, - "socket.io": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", - "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", + "sntp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", + "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", "dev": true, + "optional": true, "requires": { - "debug": "~3.1.0", - "engine.io": "~3.2.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.1.1", - "socket.io-parser": "~3.2.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } + "hoek": "0.9.x" } }, - "socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", - "dev": true - }, - "socket.io-client": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", - "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", - "dev": true, - "requires": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "engine.io-client": "~3.2.0", - "has-binary2": "~1.0.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "~3.2.0", - "to-array": "0.1.4" + "socket.io": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.2.tgz", + "integrity": "sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==", + "dev": true, + "requires": { + "@types/cookie": "^0.4.0", + "@types/cors": "^2.8.8", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.1", + "engine.io": "~4.1.0", + "socket.io-adapter": "~2.1.0", + "socket.io-parser": "~4.0.3" }, "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "2.1.2" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, + "socket.io-adapter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz", + "integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==", + "dev": true + }, "socket.io-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", - "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", "dev": true, "requires": { - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "isarray": "2.0.1" + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" }, "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "2.1.2" } }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "^1.0.0" - } - }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -20255,6 +54374,13 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, + "source-map-js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "dev": true, + "optional": true + }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -20269,20 +54395,36 @@ } }, "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", "dev": true, "requires": { - "source-map": "^0.5.6" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true, + "optional": true + }, "space-separated-tokens": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", @@ -20322,9 +54464,9 @@ } }, "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", + "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", "dev": true }, "split": { @@ -20336,6 +54478,12 @@ "through": "2" } }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "dev": true + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -20345,12 +54493,38 @@ "extend-shallow": "^3.0.0" } }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "requires": { + "readable-stream": "^3.0.0" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "sse-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/sse-stream/-/sse-stream-0.0.4.tgz", + "integrity": "sha1-gdov4VxUAOaCSN89HQzgIHuH/x8=", + "dev": true, + "requires": { + "through": "~2.2.7" + }, + "dependencies": { + "through": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/through/-/through-2.2.7.tgz", + "integrity": "sha1-bo4hIAGR1OtqmfbwEN9Gqhxusr0=", + "dev": true + } + } + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -20375,9 +54549,9 @@ "dev": true }, "stack-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", - "integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", "dev": true, "requires": { "escape-string-regexp": "^2.0.0" @@ -20391,11 +54565,106 @@ } } }, - "state-toggle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", - "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", - "dev": true + "standard-version": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.3.0.tgz", + "integrity": "sha512-cYxxKXhYfI3S9+CA84HmrJa9B88H56V5FQ302iFF2TNwJukJCNoU8FgWt+11YtwKFXRkQQFpepC2QOF7aDq2Ow==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "conventional-changelog": "3.1.24", + "conventional-changelog-config-spec": "2.1.0", + "conventional-changelog-conventionalcommits": "4.5.0", + "conventional-recommended-bump": "6.1.0", + "detect-indent": "^6.0.0", + "detect-newline": "^3.1.0", + "dotgitignore": "^2.1.0", + "figures": "^3.1.0", + "find-up": "^5.0.0", + "fs-access": "^1.0.1", + "git-semver-tags": "^4.0.0", + "semver": "^7.1.1", + "stringify-package": "^1.0.1", + "yargs": "^16.0.0" + }, + "dependencies": { + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + } + } }, "static-extend": { "version": "0.1.2", @@ -20423,12 +54692,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, "stream-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/stream-array/-/stream-array-1.1.2.tgz", @@ -20491,6 +54754,15 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -20533,6 +54805,15 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -20569,6 +54850,15 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -20578,44 +54868,89 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, - "streamroller": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", - "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", + "stream-splicer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-1.3.2.tgz", + "integrity": "sha1-PARBvhW5v04iYnXm3IOWR0VUZmE=", "dev": true, "requires": { - "async": "^2.6.2", - "date-format": "^2.0.0", - "debug": "^3.2.6", - "fs-extra": "^7.0.1", - "lodash": "^4.17.14" + "indexof": "0.0.1", + "inherits": "^2.0.1", + "isarray": "~0.0.1", + "readable-stream": "^1.1.13-1", + "readable-wrap": "^1.0.0", + "through2": "^1.0.0" }, "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "lodash": "^4.17.14" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "requires": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } + } + }, + "streamroller": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", + "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", + "dev": true, + "requires": { + "date-format": "^2.1.0", + "debug": "^4.1.1", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "date-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "dev": true + }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } @@ -20644,226 +54979,96 @@ } }, "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true - }, - "string-length": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", - "dev": true, - "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", "dev": true }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string.prototype.padend": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz", - "integrity": "sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "string.prototype.trimend": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", - "integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" + "safe-buffer": "~5.2.0" }, "dependencies": { - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-callable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "object.assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", - "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.0", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true } } }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=", "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } + "optional": true }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, - "string.prototype.trimstart": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz", - "integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==", + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-callable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "object.assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", - "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.0", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - } + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, "stringify-entities": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.2.tgz", - "integrity": "sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.1.0.tgz", + "integrity": "sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg==", "dev": true, "requires": { "character-entities-html4": "^1.0.0", "character-entities-legacy": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-hexadecimal": "^1.0.0" + "xtend": "^4.0.0" } }, + "stringify-package": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", + "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", + "dev": true + }, + "stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", + "dev": true, + "optional": true + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -20895,18 +55100,18 @@ "dev": true }, "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "requires": { - "get-stdin": "^4.0.1" + "min-indent": "^1.0.0" } }, "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "subarg": { @@ -20943,65 +55148,55 @@ "es6-symbol": "^3.1.1" } }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true + "syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "dev": true, + "requires": { + "acorn-node": "^1.2.0" + } }, "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", "dev": true, "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" }, "dependencies": { - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "ajv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", + "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" } }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true } } }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, "tapable": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", @@ -21009,24 +55204,24 @@ "dev": true }, "tar-fs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", - "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, "requires": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.0.0" + "tar-stream": "^2.1.4" } }, "tar-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", - "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "requires": { - "bl": "^4.0.1", + "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", @@ -21043,155 +55238,74 @@ } }, "ternary-stream": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.1.1.tgz", - "integrity": "sha512-j6ei9hxSoyGlqTmoMjOm+QNvUKDOIY6bNl4Uh1lhBvl6yjPW2iLqxDUYyfDPZknQ4KdRziFl+ec99iT4l7g0cw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-3.0.0.tgz", + "integrity": "sha512-oIzdi+UL/JdktkT+7KU5tSIQjj8pbShj3OASuvDEhm0NT5lppsm7aXWAmAq4/QMaBIyfuEcNLbAQA+HpaISobQ==", "dev": true, "requires": { - "duplexify": "^3.5.0", + "duplexify": "^4.1.1", "fork-stream": "^0.0.4", - "merge-stream": "^1.0.0", - "through2": "^2.0.1" + "merge-stream": "^2.0.0", + "through2": "^3.0.1" }, "dependencies": { - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", "dev": true, "requires": { - "readable-stream": "^2.0.1" + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" } }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + } } } } }, - "test-exclude": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", - "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "terser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.4.0.tgz", + "integrity": "sha512-3dZunFLbCJis9TAF2VnX+VrQLctRUmt1p3W2kCsJuZE4ZgWqh//+1MZ62EanewrqKoUf4zIaDGZAvml4UDc0OQ==", "dev": true, "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^2.0.0" + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" }, "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", - "dev": true, - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true } } }, + "text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "dev": true + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -21199,15 +55313,15 @@ "dev": true }, "textextensions": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz", - "integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz", + "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", "dev": true }, - "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", + "throttleit": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", + "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", "dev": true }, "through": { @@ -21217,13 +55331,22 @@ "dev": true }, "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "3" + } + }, + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" }, "dependencies": { "readable-stream": { @@ -21240,19 +55363,28 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } } } }, - "through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, "time-stamp": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", @@ -21260,15 +55392,15 @@ "dev": true }, "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", + "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=", "dev": true }, "timers-browserify": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", - "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", "dev": true, "requires": { "setimmediate": "^1.0.4" @@ -21304,18 +55436,18 @@ }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { "ms": "^2.1.1" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true } } @@ -21329,12 +55461,6 @@ "os-tmpdir": "~1.0.2" } }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, "to-absolute-glob": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", @@ -21345,12 +55471,6 @@ "is-negated-glob": "^1.0.0" } }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -21363,6 +55483,12 @@ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, + "to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", + "dev": true + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -21372,12 +55498,6 @@ "kind-of": "^3.0.2" }, "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -21417,6 +55537,42 @@ "dev": true, "requires": { "through2": "^2.0.3" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, "toidentifier": { @@ -21434,49 +55590,28 @@ "punycode": "^2.1.1" } }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, "traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", "dev": true }, - "trim": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", - "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", - "dev": true - }, - "trim-lines": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-1.1.3.tgz", - "integrity": "sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA==", + "treeify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.0.1.tgz", + "integrity": "sha1-abPNAiAioWhCTnz6HO1EyTnT6y8=", "dev": true }, "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, - "trim-trailing-lines": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", - "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==", + "trim-off-newlines": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.3.tgz", + "integrity": "sha512-kh6Tu6GbeSNMGfrrZh6Bb/4ZEHV1QlB4xNDBeog8Y9/QwFlKTRyWvY3Fs9tRDAMZliVUwieMgEdIeL/FtqjkJg==", "dev": true }, "trough": { @@ -21485,6 +55620,65 @@ "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", "dev": true }, + "trumpet": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/trumpet/-/trumpet-1.6.6.tgz", + "integrity": "sha1-6Oq9R8lYtWChIG2bm2yEtH6oA+Q=", + "dev": true, + "requires": { + "duplexer2": "~0.0.2", + "html-select": "^2.3.5", + "html-tokenize": "^1.1.1", + "inherits": "^2.0.0", + "readable-stream": "^1.0.27-1", + "through2": "^1.0.0" + }, + "dependencies": { + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "requires": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } + } + }, "tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -21521,9 +55715,9 @@ } }, "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, "tty-browserify": { @@ -21554,12 +55748,12 @@ "dev": true }, "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "^1.2.1" } }, "type-detect": { @@ -21569,9 +55763,9 @@ "dev": true }, "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true }, "type-is": { @@ -21611,17 +55805,24 @@ } }, "ua-parser-js": { - "version": "0.7.21", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", - "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==", + "version": "0.7.28", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", + "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==", "dev": true }, - "uglify-js": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz", - "integrity": "sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==", + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, + "uglify-js": { + "version": "3.13.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.9.tgz", + "integrity": "sha512-wZbyTQ1w6Y7fHdt8sJnHfSIuWeDgk6B5rCb4E/AM6QNNPbOMIZph21PW5dRB3h7Df0GszN+t7RuUH6sWK5bF0g==", + "dev": true, + "optional": true + }, "uglify-to-browserify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", @@ -21656,6 +55857,12 @@ "wordwrap": "0.0.2" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", @@ -21687,11 +55894,88 @@ } } }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", - "dev": true + "umd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/umd/-/umd-2.1.0.tgz", + "integrity": "sha1-SmMHt2LxfwLSAbX6FU5nM5bCY88=", + "dev": true, + "requires": { + "rfile": "~1.0.0", + "ruglify": "~1.0.0", + "through": "~2.3.4", + "uglify-js": "~2.4.0" + }, + "dependencies": { + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "source-map": { + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz", + "integrity": "sha1-p8/omux7FoLDsZjQrPtH19CQVms=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "uglify-js": { + "version": "2.4.24", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.24.tgz", + "integrity": "sha1-+tV1XB4Vd2WLsG/5q25UjJW+vW4=", + "dev": true, + "requires": { + "async": "~0.2.6", + "source-map": "0.1.34", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.5.4" + } + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "yargs": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.5.4.tgz", + "integrity": "sha1-2K/49mXpTDS9JZvevRv68N3TU2E=", + "dev": true, + "requires": { + "camelcase": "^1.0.2", + "decamelize": "^1.0.0", + "window-size": "0.1.0", + "wordwrap": "0.0.2" + } + } + } + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } }, "unbzip2-stream": { "version": "1.4.3", @@ -21709,10 +55993,22 @@ "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", "dev": true }, + "underscore": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz", + "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==", + "dev": true + }, + "underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", + "dev": true + }, "undertaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", "dev": true, "requires": { "arr-flatten": "^1.0.1", @@ -21720,10 +56016,19 @@ "bach": "^1.0.0", "collection-map": "^1.0.0", "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", "last-run": "^1.1.0", "object.defaults": "^1.0.0", "object.reduce": "^1.0.0", "undertaker-registry": "^1.0.0" + }, + "dependencies": { + "fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", + "dev": true + } } }, "undertaker-registry": { @@ -21732,16 +56037,6 @@ "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", "dev": true }, - "unherit": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", - "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", - "dev": true, - "requires": { - "inherits": "^2.0.0", - "xtend": "^4.0.0" - } - }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -21771,17 +56066,25 @@ "dev": true }, "unified": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", - "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.1.tgz", + "integrity": "sha512-juWjuI8Z4xFg8pJbnEZ41b5xjGUWGHqXALmBZ3FC3WX0PIx1CZBIIJ6mXbYMcf6Yw4Fi0rFUTA1cdz/BglbOhA==", "dev": true, "requires": { "bail": "^1.0.0", "extend": "^3.0.0", - "is-plain-obj": "^1.1.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", "trough": "^1.0.0", - "vfile": "^2.0.0", - "x-is-string": "^0.1.0" + "vfile": "^4.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + } } }, "union-value": { @@ -21807,24 +56110,21 @@ } }, "unist-builder": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-1.0.4.tgz", - "integrity": "sha512-v6xbUPP7ILrT15fHGrNyHc1Xda8H3xVhP7/HAIotHOhVPjH5dCXA097C3Rry1Q2O+HbOLCao4hfPB+EYEjHgVg==", - "dev": true, - "requires": { - "object-assign": "^4.1.0" - } + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", + "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==", + "dev": true }, "unist-util-generated": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.5.tgz", - "integrity": "sha512-1TC+NxQa4N9pNdayCYA1EGUOCAO0Le3fVp7Jzns6lnua/mYgwHo0tz5WUAfrdpNch1RZLHc61VZ1SDgrtNXLSw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz", + "integrity": "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==", "dev": true }, "unist-util-is": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", - "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", "dev": true }, "unist-util-position": { @@ -21833,43 +56133,40 @@ "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==", "dev": true }, - "unist-util-remove-position": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", - "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", + "unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", "dev": true, "requires": { - "unist-util-visit": "^1.1.0" + "@types/unist": "^2.0.2" } }, - "unist-util-stringify-position": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", - "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", - "dev": true - }, "unist-util-visit": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", - "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", "dev": true, "requires": { - "unist-util-visit-parents": "^2.0.0" + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" } }, "unist-util-visit-parents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", - "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", "dev": true, "requires": { - "unist-util-is": "^3.0.0" + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" } }, "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, "unpipe": { @@ -21954,6 +56251,15 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -21964,9 +56270,9 @@ "dev": true }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { "punycode": "^2.1.0" @@ -22003,53 +56309,26 @@ "dev": true }, "url-parse": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.0.tgz", - "integrity": "sha512-9iT6N4s93SMfzunOyDPe4vo4nLcSu1yq0IQK1gURmjm8tQNlM6loiuCRrKG1hHGXfB2EWd6H4cGi7tGdaygMFw==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", + "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", "dev": true, "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - }, - "dependencies": { - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - } - } - }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "dev": true - }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, - "useragent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", - "dev": true, - "requires": { - "lru-cache": "4.1.x", - "tmp": "0.0.x" - } + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true }, "util": { "version": "0.11.1", @@ -22066,17 +56345,11 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } + "util-extend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", + "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=", + "dev": true }, "utils-merge": { "version": "1.0.1", @@ -22084,9 +56357,15 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", - "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "v8flags": { @@ -22131,101 +56410,56 @@ } }, "vfile": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", - "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", "dev": true, "requires": { - "is-buffer": "^1.1.4", - "replace-ext": "1.0.0", - "unist-util-stringify-position": "^1.0.0", - "vfile-message": "^1.0.0" + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" }, "dependencies": { "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", "dev": true } } }, - "vfile-location": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", - "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", - "dev": true - }, "vfile-message": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", - "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", "dev": true, "requires": { - "unist-util-stringify-position": "^1.1.1" + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" } }, "vfile-reporter": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-4.0.0.tgz", - "integrity": "sha1-6m8K4TQvSEFXOYXgX5QXNvJ96do=", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-6.0.2.tgz", + "integrity": "sha512-GN2bH2gs4eLnw/4jPSgfBjo+XCuvnX9elHICJZjVD4+NM0nsUrMTvdjGY5Sc/XG69XVTgLwj7hknQVc6M9FukA==", "dev": true, "requires": { "repeat-string": "^1.5.0", - "string-width": "^1.0.0", - "supports-color": "^4.1.0", - "unist-util-stringify-position": "^1.0.0", + "string-width": "^4.0.0", + "supports-color": "^6.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-sort": "^2.1.2", "vfile-statistics": "^1.1.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { - "has-flag": "^2.0.0" + "has-flag": "^3.0.0" } } } @@ -22243,9 +56477,9 @@ "dev": true }, "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", "dev": true, "requires": { "clone": "^2.1.1", @@ -22303,6 +56537,25 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } } } }, @@ -22353,13 +56606,15 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "vue-template-compiler": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", + "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==", "dev": true, + "optional": true, "requires": { - "browser-process-hrtime": "^1.0.0" + "de-indent": "^1.0.2", + "he": "^1.1.0" } }, "walk": { @@ -22371,31 +56626,22 @@ "foreachasync": "^3.0.0" } }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, "watchpack": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", - "integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", "dev": true, "requires": { - "chokidar": "^3.4.0", + "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", "neo-async": "^2.5.0", - "watchpack-chokidar2": "^2.0.0" + "watchpack-chokidar2": "^2.0.1" } }, "watchpack-chokidar2": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", - "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", "dev": true, "optional": true, "requires": { @@ -22553,13 +56799,6 @@ "binary-extensions": "^1.0.0" } }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "optional": true - }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -22632,6 +56871,16 @@ "readable-stream": "^2.0.2" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -22650,40 +56899,117 @@ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "dev": true, - "optional": true, "requires": { "defaults": "^1.0.3" } }, "webdriver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.3.0.tgz", - "integrity": "sha512-osHp5DX8eQ76Sy6/UYoECmDnUXwLhy5tlBeJh86er7S04FLAlmEqCvYXgxTSb8YtDjh9Xt9gP768RGhxR7ik5A==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.7.4.tgz", + "integrity": "sha512-bE6/A+OYb040GZ1MiuZebc8bOOYm797dmqEfmj6aoEQ4BMy1juiFlzCzeBzAlPrq33qPa8/CSYfH7rnkB3RRwg==", "dev": true, "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/protocols": "6.3.0", - "@wdio/utils": "6.3.0", + "@types/node": "^14.14.31", + "@wdio/config": "7.7.3", + "@wdio/logger": "7.7.0", + "@wdio/protocols": "7.7.4", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", "got": "^11.0.2", "lodash.merge": "^4.6.1" + }, + "dependencies": { + "@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true + }, + "@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "webdriverio": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.3.4.tgz", - "integrity": "sha512-/53xQEituEFTaJtZMgg5Uz3GXY1Otqyry0LA8dYLYUNkTK0yCa26DL4ycDnWE0i9wEYNFX6YHCgiqTJjHEjKAg==", - "dev": true, - "requires": { - "@types/puppeteer": "^3.0.1", - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/repl": "6.3.0", - "@wdio/utils": "6.3.0", - "archiver": "^4.0.1", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.7.4.tgz", + "integrity": "sha512-VSWRj2mmvA8WbideFAYb5BMWPkBCJ7gJHhYrUSibTrMHKreRtX++cw/oGxxowy9/pTHsAW6OxlnaDxFL5Gt08A==", + "dev": true, + "requires": { + "@types/aria-query": "^4.2.1", + "@types/node": "^14.14.31", + "@wdio/config": "7.7.3", + "@wdio/logger": "7.7.0", + "@wdio/protocols": "7.7.4", + "@wdio/repl": "7.7.3", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", + "archiver": "^5.0.0", + "aria-query": "^4.2.2", "atob": "^2.1.2", + "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "6.3.4", + "devtools": "7.7.4", + "devtools-protocol": "^0.0.892017", + "fs-extra": "^10.0.0", "get-port": "^5.1.1", "grapheme-splitter": "^1.0.2", "lodash.clonedeep": "^4.5.0", @@ -22691,19 +57017,83 @@ "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", "minimatch": "^3.0.4", - "puppeteer-core": "^5.1.0", - "resq": "^1.6.0", - "rgb2hex": "^0.2.0", - "serialize-error": "^7.0.0", - "webdriver": "6.3.0" + "puppeteer-core": "^9.1.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^8.0.0", + "webdriver": "7.7.4" + }, + "dependencies": { + "@types/node": { + "version": "14.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", + "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "dev": true + }, + "@wdio/logger": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.7.0.tgz", + "integrity": "sha512-XX/OkC8NlvsBdhKsb9j7ZbuQtF/Vuo0xf38PXdqYtVezOrYbDuba0hPG++g/IGNuAF34ZbSi+49cvz4u5w92kQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, "webpack": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.12.0.tgz", @@ -22734,10 +57124,16 @@ "yargs": "^8.0.2" }, "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -22791,6 +57187,38 @@ } } }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -22806,6 +57234,12 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", @@ -22880,6 +57314,17 @@ "minimist": "^1.2.5" } }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -22946,6 +57391,21 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -23003,6 +57463,15 @@ "has-flag": "^2.0.0" } }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -23027,9 +57496,9 @@ } }, "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", "dev": true }, "yargs": { @@ -23065,9 +57534,9 @@ } }, "webpack-bundle-analyzer": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.8.0.tgz", - "integrity": "sha512-PODQhAYVEourCcOuU+NiYI7WdR8QyELZGgPvB1y2tjbUpbmcQOt5Q7jEK+ttd5se0KSBKD9SXHCEozS++Wllmw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz", + "integrity": "sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==", "dev": true, "requires": { "acorn": "^7.1.1", @@ -23079,30 +57548,12 @@ "express": "^4.16.3", "filesize": "^3.6.1", "gzip-size": "^5.0.0", - "lodash": "^4.17.15", + "lodash": "^4.17.19", "mkdirp": "^0.5.1", "opener": "^1.5.1", "ws": "^6.0.0" }, "dependencies": { - "acorn": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", - "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", - "dev": true - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "ejs": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", @@ -23119,9 +57570,9 @@ } }, "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", "dev": true, "requires": { "async-limiter": "~1.0.0" @@ -23172,9 +57623,9 @@ }, "dependencies": { "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", "dev": true } } @@ -23382,6 +57833,12 @@ "sha.js": "2.2.6" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", @@ -23518,12 +57975,6 @@ "binary-extensions": "^1.0.0" } }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", @@ -23965,6 +58416,15 @@ "integrity": "sha1-F93t3F9yL7ZlAWWIlUYZd4ZzFbo=", "dev": true }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "supports-color": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", @@ -24118,52 +58578,26 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" } }, "which-boxed-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", - "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, "requires": { - "is-bigint": "^1.0.0", - "is-boolean-object": "^1.0.0", - "is-number-object": "^1.0.3", - "is-string": "^1.0.4", - "is-symbol": "^1.0.2" + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" } }, "which-collection": { @@ -24185,13 +58619,14 @@ "dev": true }, "which-typed-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", - "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", + "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", "dev": true, "requires": { "available-typed-arrays": "^1.0.2", - "es-abstract": "^1.17.5", + "call-bind": "^1.0.0", + "es-abstract": "^1.18.0-next.1", "foreach": "^2.0.5", "function-bind": "^1.1.1", "has-symbols": "^1.0.1", @@ -24259,15 +58694,15 @@ "dev": true }, "workerpool": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", - "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.4.tgz", + "integrity": "sha512-jGWPzsUqzkow8HoAvqaPWTUPCrlPJaJ5tY8Iz7n1uCz3tTp6s3CDG0FF1NsX42WNlkRSW6Mr+CDZGnNoSsKa7g==", "dev": true }, "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { "ansi-styles": "^4.0.0", @@ -24276,12 +58711,11 @@ }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -24328,39 +58762,23 @@ } } }, - "write-file-atomic": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", - "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", - "dev": true - }, - "x-is-string": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", - "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", - "dev": true + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz", + "integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==", + "dev": true, + "requires": {} }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "xml-escape": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.0.0.tgz", + "integrity": "sha1-AJY9aXsq3wwYXE4E5zF0upsojrI=", "dev": true }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", "dev": true }, "xtend": { @@ -24370,9 +58788,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, "yallist": { @@ -24388,147 +58806,28 @@ "dev": true }, "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true }, "yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, @@ -24595,22 +58894,28 @@ "fd-slicer": "~1.1.0" } }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true }, "zip-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-3.0.1.tgz", - "integrity": "sha512-r+JdDipt93ttDjsOVPU5zaq5bAyY+3H19bDrThkvuVxC0xMQzU1PJcS6D+KrP3u96gH9XLomcHPb+2skoDjulQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", "dev": true, "requires": { "archiver-utils": "^2.1.0", - "compress-commons": "^3.0.0", + "compress-commons": "^4.1.0", "readable-stream": "^3.6.0" } + }, + "zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "dev": true } } } diff --git a/package.json b/package.json index 05f68f4a009..eca229766c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.40.0-pre", + "version": "6.13.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -11,6 +11,11 @@ "type": "git", "url": "https://github.com/prebid/Prebid.js.git" }, + "browserslist": [ + "> 0.25%", + "not IE 11", + "not op_mini all" + ], "keywords": [ "advertising", "auction", @@ -25,31 +30,33 @@ }, "devDependencies": { "@babel/core": "^7.8.4", + "@babel/eslint-parser": "^7.16.5", "@babel/preset-env": "^7.8.4", "@jsdevtools/coverage-istanbul-loader": "^3.0.3", "@wdio/browserstack-service": "^6.1.4", - "@wdio/cli": "^6.1.5", - "@wdio/concise-reporter": "^6.1.5", - "@wdio/local-runner": "^6.1.7", - "@wdio/mocha-framework": "^6.1.6", - "@wdio/spec-reporter": "^6.1.5", - "@wdio/sync": "^6.1.5", + "@wdio/cli": "^7.5.2", + "@wdio/concise-reporter": "^7.5.2", + "@wdio/local-runner": "^7.5.2", + "@wdio/mocha-framework": "^7.5.2", + "@wdio/spec-reporter": "^7.5.2", + "@wdio/sync": "^7.5.2", "ajv": "5.5.2", "babel-loader": "^8.0.5", "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", "deep-equal": "^2.0.3", - "documentation": "^5.2.2", + "documentation": "^13.2.5", "es5-shim": "^4.5.14", + "eslint": "^7.27.0", "eslint-config-standard": "^10.2.1", "eslint-plugin-import": "^2.20.2", - "eslint-plugin-node": "^5.1.0", + "eslint-plugin-node": "^11.1.0", "eslint-plugin-prebid": "file:./plugins/eslint", - "eslint-plugin-promise": "^3.5.0", + "eslint-plugin-promise": "^5.1.0", "eslint-plugin-standard": "^3.0.1", "execa": "^1.0.0", - "faker": "^3.1.0", + "faker": "^5.5.3", "fs.extra": "^1.3.2", "gulp": "^4.0.0", "gulp-clean": "^0.3.2", @@ -57,34 +64,34 @@ "gulp-connect": "^5.7.0", "gulp-eslint": "^4.0.0", "gulp-footer": "^2.0.2", - "gulp-header": "^1.7.1", - "gulp-if": "^2.0.2", + "gulp-header": "^2.0.9", + "gulp-if": "^3.0.0", "gulp-js-escape": "^1.0.1", "gulp-replace": "^1.0.0", - "gulp-shell": "^0.5.2", - "gulp-sourcemaps": "^2.6.0", - "gulp-uglify": "^3.0.0", + "gulp-shell": "^0.8.0", + "gulp-sourcemaps": "^3.0.0", + "gulp-terser": "^2.0.1", "gulp-util": "^3.0.0", - "is-docker": "^1.1.0", + "is-docker": "^2.2.1", "istanbul": "^0.4.5", - "karma": "^4.0.0", - "karma-babel-preprocessor": "^6.0.1", + "karma": "^6.3.2", + "karma-babel-preprocessor": "^8.0.1", "karma-browserstack-launcher": "1.4.0", "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^2.2.0", + "karma-chrome-launcher": "^3.1.0", "karma-coverage": "^2.0.1", - "karma-coverage-istanbul-reporter": "^1.3.0", + "karma-coverage-istanbul-reporter": "^3.0.3", "karma-es5-shim": "^0.0.4", - "karma-firefox-launcher": "^1.3.0", + "karma-firefox-launcher": "^2.1.0", "karma-ie-launcher": "^1.0.0", - "karma-mocha": "^1.3.0", + "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "karma-opera-launcher": "^1.0.0", "karma-safari-launcher": "^1.0.0", "karma-script-launcher": "^1.0.0", "karma-sinon": "^1.0.5", "karma-sourcemap-loader": "^0.3.7", - "karma-spec-reporter": "^0.0.31", + "karma-spec-reporter": "^0.0.32", "karma-webpack": "^3.0.5", "lodash": "^4.17.21", "mocha": "^5.0.0", @@ -92,9 +99,9 @@ "opn": "^5.4.0", "resolve-from": "^5.0.0", "sinon": "^4.1.3", - "through2": "^2.0.3", + "through2": "^4.0.2", "url-parse": "^1.0.5", - "webdriverio": "^6.1.5", + "webdriverio": "^7.6.1", "webpack": "^3.0.0", "webpack-bundle-analyzer": "^3.8.0", "webpack-stream": "^3.2.0", @@ -102,15 +109,14 @@ }, "dependencies": { "babel-plugin-transform-object-assign": "^6.22.0", - "core-js": "^3.0.0", - "core-js-pure": "^3.6.5", + "core-js": "^3.13.0", + "core-js-pure": "^3.13.0", "criteo-direct-rsa-validate": "^1.1.0", "crypto-js": "^3.3.0", "dlv": "1.1.3", "dset": "2.0.1", "express": "^4.15.4", "fun-hooks": "^0.9.9", - "jsencrypt": "^3.0.0-rc.1", "just-clone": "^1.0.2", "live-connect-js": "2.0.0" } diff --git a/plugins/eslint/validateImports.js b/plugins/eslint/validateImports.js index a39bf9b26d5..37a87fffb50 100644 --- a/plugins/eslint/validateImports.js +++ b/plugins/eslint/validateImports.js @@ -62,7 +62,7 @@ module.exports = { let importPath = node.source.value.trim(); flagErrors(context, node, importPath); }, - "ExportNamedDeclaration[source]"(node) { + 'ExportNamedDeclaration[source]'(node) { let importPath = node.source.value.trim(); flagErrors(context, node, importPath); } diff --git a/plugins/pbjsGlobals.js b/plugins/pbjsGlobals.js index bf3c9033ee6..79dafd1e8b2 100644 --- a/plugins/pbjsGlobals.js +++ b/plugins/pbjsGlobals.js @@ -1,11 +1,13 @@ let t = require('@babel/core').types; let prebid = require('../package.json'); +const path = require('path'); module.exports = function(api, options) { + const pbGlobal = options.globalVarName || prebid.globalVarName; let replace = { '$prebid.version$': prebid.version, - '$$PREBID_GLOBAL$$': options.globalVarName || prebid.globalVarName, + '$$PREBID_GLOBAL$$': pbGlobal, '$$REPO_AND_VERSION$$': `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}` }; @@ -13,8 +15,33 @@ module.exports = function(api, options) { '$$REPO_AND_VERSION$$' ]; + const PREBID_ROOT = path.resolve(__dirname, '..'); + + function getModuleName(filename) { + const modPath = path.parse(path.relative(PREBID_ROOT, filename)); + if (modPath.ext.toLowerCase() !== '.js') { + return null; + } + if (modPath.dir === 'modules') { + // modules/moduleName.js -> moduleName + return modPath.name; + } + if (modPath.name.toLowerCase() === 'index' && path.dirname(modPath.dir) === 'modules') { + // modules/moduleName/index.js -> moduleName + return path.basename(modPath.dir); + } + return null; + } + return { visitor: { + Program(path, state) { + const modName = getModuleName(state.filename); + if (modName != null) { + // append "registration" of module file to $$PREBID_GLOBAL$$.installedModules + path.node.body.push(...api.parse(`window.${pbGlobal}.installedModules.push('${modName}');`, {filename: state.filename}).program.body); + } + }, StringLiteral(path) { Object.keys(replace).forEach(name => { if (path.node.value.includes(name)) { diff --git a/src/AnalyticsAdapter.js b/src/AnalyticsAdapter.js index 80c12a3eb8e..2c27307ead3 100644 --- a/src/AnalyticsAdapter.js +++ b/src/AnalyticsAdapter.js @@ -1,8 +1,8 @@ import CONSTANTS from './constants.json'; import { ajax } from './ajax.js'; +import { logMessage, _each } from './utils.js'; const events = require('./events.js'); -const utils = require('./utils.js'); const { EVENTS: { @@ -18,6 +18,7 @@ const { BIDDER_DONE, SET_TARGETING, AD_RENDER_FAILED, + AD_RENDER_SUCCEEDED, AUCTION_DEBUG, ADD_AD_UNITS } @@ -113,6 +114,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } [SET_TARGETING]: args => this.enqueue({ eventType: SET_TARGETING, args }), [AUCTION_END]: args => this.enqueue({ eventType: AUCTION_END, args }), [AD_RENDER_FAILED]: args => this.enqueue({ eventType: AD_RENDER_FAILED, args }), + [AD_RENDER_SUCCEEDED]: args => this.enqueue({ eventType: AD_RENDER_SUCCEEDED, args }), [AUCTION_DEBUG]: args => this.enqueue({ eventType: AUCTION_DEBUG, args }), [ADD_AD_UNITS]: args => this.enqueue({ eventType: ADD_AD_UNITS, args }), [AUCTION_INIT]: args => { @@ -121,22 +123,22 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } } }; - utils._each(_handlers, (handler, event) => { + _each(_handlers, (handler, event) => { events.on(event, handler); }); } else { - utils.logMessage(`Analytics adapter for "${global}" disabled by sampling`); + logMessage(`Analytics adapter for "${global}" disabled by sampling`); } // finally set this function to return log message, prevents multiple adapter listeners this._oldEnable = this.enableAnalytics; this.enableAnalytics = function _enable() { - return utils.logMessage(`Analytics adapter for "${global}" already enabled, unnecessary call to \`enableAnalytics\`.`); + return logMessage(`Analytics adapter for "${global}" already enabled, unnecessary call to \`enableAnalytics\`.`); }; } function _disable() { - utils._each(_handlers, (handler, event) => { + _each(_handlers, (handler, event) => { events.off(event, handler); }); this.enableAnalytics = this._oldEnable ? this._oldEnable : _enable; @@ -156,6 +158,6 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } _enableCheck = false; } - utils.logMessage(`event count sent to ${global}: ${_eventCount}`); + logMessage(`event count sent to ${global}: ${_eventCount}`); } } diff --git a/src/Renderer.js b/src/Renderer.js index c997658b30b..1d7506418bb 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -1,5 +1,7 @@ import { loadExternalScript } from './adloader.js'; -import * as utils from './utils.js'; +import { + logError, logWarn, logMessage, deepAccess +} from './utils.js'; import find from 'core-js-pure/features/array/find.js'; const moduleCode = 'outstream'; @@ -25,7 +27,7 @@ export function Renderer(options) { this.cmd = []; this.push = func => { if (typeof func !== 'function') { - utils.logError('Commands given to Renderer.push must be wrapped in a function'); + logError('Commands given to Renderer.push must be wrapped in a function'); return; } this.loaded ? func.call() : this.cmd.push(func); @@ -44,7 +46,7 @@ export function Renderer(options) { if (this._render) { this._render.apply(this, renderArgs) } else { - utils.logWarn(`No render function was provided, please use .setRender on the renderer`); + logWarn(`No render function was provided, please use .setRender on the renderer`); } } @@ -53,7 +55,7 @@ export function Renderer(options) { this.cmd.unshift(runRender) // should render run first ? loadExternalScript(url, moduleCode, this.callback); } else { - utils.logWarn(`External Js not loaded by Renderer since renderer url and callback is already defined on adUnit ${adUnitCode}`); + logWarn(`External Js not loaded by Renderer since renderer url and callback is already defined on adUnit ${adUnitCode}`); runRender() } }.bind(this) // bind the function to this object to avoid 'this' errors @@ -80,7 +82,7 @@ Renderer.prototype.handleVideoEvent = function({ id, eventName }) { this.handlers[eventName](); } - utils.logMessage(`Prebid Renderer event for id ${id} type ${eventName}`); + logMessage(`Prebid Renderer event for id ${id} type ${eventName}`); }; /* @@ -92,7 +94,7 @@ Renderer.prototype.process = function() { try { this.cmd.shift().call(); } catch (error) { - utils.logError('Error processing Renderer command: ', error); + logError('Error processing Renderer command: ', error); } } }; @@ -126,11 +128,11 @@ function isRendererPreferredFromAdUnit(adUnitCode) { } // renderer defined at adUnit level - const adUnitRenderer = utils.deepAccess(adUnit, 'renderer'); + const adUnitRenderer = deepAccess(adUnit, 'renderer'); const hasValidAdUnitRenderer = !!(adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render); // renderer defined at adUnit.mediaTypes level - const mediaTypeRenderer = utils.deepAccess(adUnit, 'mediaTypes.video.renderer'); + const mediaTypeRenderer = deepAccess(adUnit, 'mediaTypes.video.renderer'); const hasValidMediaTypeRenderer = !!(mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render) return !!( diff --git a/src/adRendering.js b/src/adRendering.js new file mode 100644 index 00000000000..3bd4b0b918f --- /dev/null +++ b/src/adRendering.js @@ -0,0 +1,38 @@ +import {logError} from './utils.js'; +import events from './events.js'; +import CONSTANTS from './constants.json'; + +const {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED} = CONSTANTS.EVENTS; + +/** + * Emit the AD_RENDER_FAILED event. + * + * @param reason one of the values in CONSTANTS.AD_RENDER_FAILED_REASON + * @param message failure description + * @param bid? bid response object that failed to render + * @param id? adId that failed to render + */ +export function emitAdRenderFail({ reason, message, bid, id }) { + const data = { reason, message }; + if (bid) data.bid = bid; + if (id) data.adId = id; + + logError(message); + events.emit(AD_RENDER_FAILED, data); +} + +/** + * Emit the AD_RENDER_SUCCEEDED event. + * + * @param doc document object that was used to `.write` the ad. Should be `null` if unavailable (e.g. for documents in + * a cross-origin frame). + * @param bid bid response object for the ad that was rendered + * @param id adId that was rendered. + */ +export function emitAdRenderSucceeded({ doc, bid, id }) { + const data = { doc }; + if (bid) data.bid = bid; + if (id) data.adId = id; + + events.emit(AD_RENDER_SUCCEEDED, data); +} diff --git a/src/adapterManager.js b/src/adapterManager.js index 5f8f2e5721c..7480f2a56f6 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -1,8 +1,29 @@ /** @module adaptermanger */ -import { flatten, getBidderCodes, getDefinedParams, shuffle, timestamp, getBidderRequest, bind } from './utils.js'; +import { + _each, + bind, + deepAccess, + deepClone, + flatten, + generateUUID, + getBidderCodes, + getDefinedParams, + getUniqueIdentifierStr, + getUserConfiguredParams, + groupBy, + isArray, + isValidMediaTypes, + logError, + logInfo, + logMessage, + logWarn, + shuffle, + timestamp, + isStr +} from './utils.js'; import { getLabels, resolveStatus } from './sizeMapping.js'; -import { processNativeAdUnitParams, nativeAdapters } from './native.js'; +import { decorateAdUnitsWithNativeParams, nativeAdapters } from './native.js'; import { newBidder } from './adapters/bidderFactory.js'; import { ajaxBuilder } from './ajax.js'; import { config, RANDOM } from './config.js'; @@ -12,7 +33,6 @@ import find from 'core-js-pure/features/array/find.js'; import { adunitCounter } from './adUnits.js'; import { getRefererInfo } from './refererDetection.js'; -var utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); var events = require('./events.js'); let s2sTestingModule; // store s2sTesting module if it's loaded @@ -25,7 +45,7 @@ let _aliasRegistry = adapterManager.aliasRegistry = {}; let _s2sConfigs = []; config.getConfig('s2sConfig', config => { if (config && config.s2sConfig) { - _s2sConfigs = Array.isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig]; + _s2sConfigs = isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig]; } }); @@ -51,23 +71,16 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) ); if (!active) { - utils.logInfo(`Size mapping disabled adUnit "${adUnit.code}"`); + logInfo(`Size mapping disabled adUnit "${adUnit.code}"`); } else if (filterResults) { - utils.logInfo(`Size mapping filtered adUnit "${adUnit.code}" banner sizes from `, filterResults.before, 'to ', filterResults.after); + logInfo(`Size mapping filtered adUnit "${adUnit.code}" banner sizes from `, filterResults.before, 'to ', filterResults.after); } if (active) { result.push(adUnit.bids.filter(bid => bid.bidder === bidderCode) .reduce((bids, bid) => { - const nativeParams = - adUnit.nativeParams || utils.deepAccess(adUnit, 'mediaTypes.native'); - if (nativeParams) { - bid = Object.assign({}, bid, { - nativeParams: processNativeAdUnitParams(nativeParams), - }); - } - bid = Object.assign({}, bid, getDefinedParams(adUnit, [ + 'nativeParams', 'ortb2Imp', 'mediaType', 'renderer', @@ -81,17 +94,17 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) } = resolveStatus(getLabels(bid, labels), filteredMediaTypes); if (!active) { - utils.logInfo(`Size mapping deactivated adUnit "${adUnit.code}" bidder "${bid.bidder}"`); + logInfo(`Size mapping deactivated adUnit "${adUnit.code}" bidder "${bid.bidder}"`); } else if (filterResults) { - utils.logInfo(`Size mapping filtered adUnit "${adUnit.code}" bidder "${bid.bidder}" banner sizes from `, filterResults.before, 'to ', filterResults.after); + logInfo(`Size mapping filtered adUnit "${adUnit.code}" bidder "${bid.bidder}" banner sizes from `, filterResults.before, 'to ', filterResults.after); } - if (utils.isValidMediaTypes(mediaTypes)) { + if (isValidMediaTypes(mediaTypes)) { bid = Object.assign({}, bid, { mediaTypes }); } else { - utils.logError( + logError( `mediaTypes is not correctly configured for adunit ${adUnit.code}` ); } @@ -100,8 +113,8 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) bids.push(Object.assign({}, bid, { adUnitCode: adUnit.code, transactionId: adUnit.transactionId, - sizes: utils.deepAccess(mediaTypes, 'banner.sizes') || utils.deepAccess(mediaTypes, 'video.playerSize') || [], - bidId: bid.bid_id || utils.getUniqueIdentifierStr(), + sizes: deepAccess(mediaTypes, 'banner.sizes') || deepAccess(mediaTypes, 'video.playerSize') || [], + bidId: bid.bid_id || getUniqueIdentifierStr(), bidderRequestId, auctionId, src, @@ -122,7 +135,7 @@ const hookedGetBids = hook('sync', getBids, 'getBids'); function getAdUnitCopyForPrebidServer(adUnits, s2sConfig) { let adaptersServerSide = s2sConfig.bidders; - let adUnitsCopy = utils.deepClone(adUnits); + let adUnitsCopy = deepClone(adUnits); adUnitsCopy.forEach((adUnit) => { // filter out client side bids @@ -130,7 +143,7 @@ function getAdUnitCopyForPrebidServer(adUnits, s2sConfig) { return includes(adaptersServerSide, bid.bidder) && (!doingS2STesting(s2sConfig) || bid.finalSource !== s2sTestingModule.CLIENT); }).map((bid) => { - bid.bid_id = utils.getUniqueIdentifierStr(); + bid.bid_id = getUniqueIdentifierStr(); return bid; }); }); @@ -143,7 +156,7 @@ function getAdUnitCopyForPrebidServer(adUnits, s2sConfig) { } function getAdUnitCopyForClientAdapters(adUnits) { - let adUnitsClientCopy = utils.deepClone(adUnits); + let adUnitsClientCopy = deepClone(adUnits); // filter out s2s bids adUnitsClientCopy.forEach((adUnit) => { adUnit.bids = adUnit.bids.filter((bid) => { @@ -161,21 +174,43 @@ function getAdUnitCopyForClientAdapters(adUnits) { export let gdprDataHandler = { consentData: null, - setConsentData: function(consentInfo) { + generatedTime: null, + setConsentData: function(consentInfo, time = timestamp()) { gdprDataHandler.consentData = consentInfo; + gdprDataHandler.generatedTime = time; }, getConsentData: function() { return gdprDataHandler.consentData; + }, + getConsentMeta: function() { + if (gdprDataHandler.consentData && gdprDataHandler.consentData.vendorData && gdprDataHandler.generatedTime) { + return { + gdprApplies: gdprDataHandler.consentData.gdprApplies, + consentStringSize: (isStr(gdprDataHandler.consentData.vendorData.tcString)) ? gdprDataHandler.consentData.vendorData.tcString.length : 0, + generatedAt: gdprDataHandler.generatedTime, + apiVersion: gdprDataHandler.consentData.apiVersion + }; + } } }; export let uspDataHandler = { consentData: null, - setConsentData: function(consentInfo) { + generatedTime: null, + setConsentData: function(consentInfo, time = timestamp()) { uspDataHandler.consentData = consentInfo; + uspDataHandler.generatedTime = time; }, getConsentData: function() { return uspDataHandler.consentData; + }, + getConsentMeta: function() { + if (uspDataHandler.consentData && uspDataHandler.generatedTime) { + return { + usp: uspDataHandler.consentData, + generatedAt: uspDataHandler.generatedTime + } + } } }; @@ -206,6 +241,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a * @see {@link https://github.com/prebid/Prebid.js/issues/4149|Issue} */ events.emit(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, adUnits); + decorateAdUnitsWithNativeParams(adUnits); let bidderCodes = getBidderCodes(adUnits); if (config.getConfig('bidderSequence') === RANDOM) { @@ -254,20 +290,22 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a _s2sConfigs.forEach(s2sConfig => { if (s2sConfig && s2sConfig.enabled) { if ((isTestingServerOnly(s2sConfig) && adUnitsContainServerRequests(adUnits, s2sConfig))) { - utils.logWarn('testServerOnly: True. All client requests will be suppressed.'); + logWarn('testServerOnly: True. All client requests will be suppressed.'); clientBidderCodes.length = 0; } let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits, s2sConfig); - let tid = utils.generateUUID(); + + // uniquePbsTid is so we know which server to send which bids to during the callBids function + let uniquePbsTid = generateUUID(); adaptersServerSide.forEach(bidderCode => { - const bidderRequestId = utils.getUniqueIdentifierStr(); + const bidderRequestId = getUniqueIdentifierStr(); const bidderRequest = { bidderCode, auctionId, bidderRequestId, - tid, - bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), + uniquePbsTid, + bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), auctionStart: auctionStart, timeout: s2sConfig.timeout, src: CONSTANTS.S2S.SRC, @@ -298,19 +336,19 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a // client adapters let adUnitsClientCopy = getAdUnitCopyForClientAdapters(adUnits); clientBidderCodes.forEach(bidderCode => { - const bidderRequestId = utils.getUniqueIdentifierStr(); + const bidderRequestId = getUniqueIdentifierStr(); const bidderRequest = { bidderCode, auctionId, bidderRequestId, - bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsClientCopy), labels, src: 'client'}), + bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': deepClone(adUnitsClientCopy), labels, src: 'client'}), auctionStart: auctionStart, timeout: cbTimeout, refererInfo }; const adapter = _bidderRegistry[bidderCode]; if (!adapter) { - utils.logError(`Trying to make a request for bidder that does not exist: ${bidderCode}`); + logError(`Trying to make a request for bidder that does not exist: ${bidderCode}`); } if (adapter && bidderRequest.bids && bidderRequest.bids.length !== 0) { @@ -334,7 +372,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, requestCallbacks, requestBidsTimeout, onTimelyResponse) => { if (!bidRequests.length) { - utils.logWarn('callBids executed with no bidRequests. Were they filtered by labels or sizing?'); + logWarn('callBids executed with no bidRequests. Were they filtered by labels or sizing?'); return; } @@ -347,7 +385,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request serverBidRequests.forEach(serverBidRequest => { var index = -1; for (var i = 0; i < uniqueServerBidRequests.length; ++i) { - if (serverBidRequest.tid === uniqueServerBidRequests[i].tid) { + if (serverBidRequest.uniquePbsTid === uniqueServerBidRequests[i].uniquePbsTid) { index = i; break; } @@ -357,7 +395,10 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request } }); - let counter = 0 + let counter = 0; + + // $.source.tid MUST be a unique UUID and also THE SAME between all PBS Requests for a given Auction + const sourceTid = generateUUID(); _s2sConfigs.forEach((s2sConfig) => { if (s2sConfig && uniqueServerBidRequests[counter] && includes(s2sConfig.bidders, uniqueServerBidRequests[counter].bidderCode)) { // s2s should get the same client side timeout as other client side requests. @@ -367,13 +408,13 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request } : undefined); let adaptersServerSide = s2sConfig.bidders; const s2sAdapter = _bidderRegistry[s2sConfig.adapter]; - let tid = uniqueServerBidRequests[counter].tid; + let uniquePbsTid = uniqueServerBidRequests[counter].uniquePbsTid; let adUnitsS2SCopy = uniqueServerBidRequests[counter].adUnitsS2SCopy; - let uniqueServerRequests = serverBidRequests.filter(serverBidRequest => serverBidRequest.tid === tid) + let uniqueServerRequests = serverBidRequests.filter(serverBidRequest => serverBidRequest.uniquePbsTid === uniquePbsTid); if (s2sAdapter) { - let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy, s2sConfig}; + let s2sBidRequest = {tid: sourceTid, 'ad_units': adUnitsS2SCopy, s2sConfig}; if (s2sBidRequest.ad_units.length) { let doneCbs = uniqueServerRequests.map(bidRequest => { bidRequest.start = timestamp(); @@ -384,29 +425,25 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request let allBidders = s2sBidRequest.ad_units.reduce((adapters, adUnit) => { return adapters.concat((adUnit.bids || []).reduce((adapters, bid) => adapters.concat(bid.bidder), [])); }, []); - utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.filter(adapter => includes(allBidders, adapter)).join(',')}`); + logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.filter(adapter => includes(allBidders, adapter)).join(',')}`); // fire BID_REQUESTED event for each s2s bidRequest uniqueServerRequests.forEach(bidRequest => { - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + // add the new sourceTid + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, {...bidRequest, tid: sourceTid}); }); // make bid requests s2sAdapter.callBids( s2sBidRequest, serverBidRequests, - (adUnitCode, bid) => { - let bidderRequest = getBidderRequest(serverBidRequests, bid.bidderCode, adUnitCode); - if (bidderRequest) { - addBidResponse.call(bidderRequest, adUnitCode, bid) - } - }, + addBidResponse, () => doneCbs.forEach(done => done()), s2sAjax ); } } else { - utils.logError('missing ' + s2sConfig.adapter); + logError('missing ' + s2sConfig.adapter); } counter++ } @@ -418,7 +455,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request // TODO : Do we check for bid in pool from here and skip calling adapter again ? const adapter = _bidderRegistry[bidRequest.bidderCode]; config.runWithBidder(bidRequest.bidderCode, () => { - utils.logMessage(`CALLING BIDDER`); + logMessage(`CALLING BIDDER`); events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); }); let ajax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? { @@ -433,7 +470,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request adapter.callBids, adapter, bidRequest, - addBidResponse.bind(bidRequest), + addBidResponse, adapterDone, ajax, onTimelyResponse, @@ -441,7 +478,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request ) ); } catch (e) { - utils.logError(`${bidRequest.bidderCode} Bid Adapter emitted an uncaught error when parsing their bidRequest`, {e, bidRequest}); + logError(`${bidRequest.bidderCode} Bid Adapter emitted an uncaught error when parsing their bidRequest`, {e, bidRequest}); adapterDone(); } }); @@ -476,10 +513,10 @@ adapterManager.registerBidAdapter = function (bidAdapter, bidderCode, {supported nativeAdapters.push(bidderCode); } } else { - utils.logError('Bidder adaptor error for bidder code: ' + bidderCode + 'bidder must implement a callBids() function'); + logError('Bidder adaptor error for bidder code: ' + bidderCode + 'bidder must implement a callBids() function'); } } else { - utils.logError('bidAdapter or bidderCode not specified'); + logError('bidAdapter or bidderCode not specified'); } }; @@ -502,7 +539,7 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias, options) { } }); nonS2SAlias.forEach(bidderCode => { - utils.logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adapterManager.aliasBidAdapter'); + logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adapterManager.aliasBidAdapter'); }) } else { try { @@ -524,11 +561,11 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias, options) { supportedMediaTypes }); } catch (e) { - utils.logError(bidderCode + ' bidder does not currently support aliasing.', 'adapterManager.aliasBidAdapter'); + logError(bidderCode + ' bidder does not currently support aliasing.', 'adapterManager.aliasBidAdapter'); } } } else { - utils.logMessage('alias name "' + alias + '" has been already specified.'); + logMessage('alias name "' + alias + '" has been already specified.'); } }; @@ -538,25 +575,25 @@ adapterManager.registerAnalyticsAdapter = function ({adapter, code, gvlid}) { adapter.code = code; _analyticsRegistry[code] = { adapter, gvlid }; } else { - utils.logError(`Prebid Error: Analytics adaptor error for analytics "${code}" + logError(`Prebid Error: Analytics adaptor error for analytics "${code}" analytics adapter must implement an enableAnalytics() function`); } } else { - utils.logError('Prebid Error: analyticsAdapter or analyticsCode not specified'); + logError('Prebid Error: analyticsAdapter or analyticsCode not specified'); } }; adapterManager.enableAnalytics = function (config) { - if (!utils.isArray(config)) { + if (!isArray(config)) { config = [config]; } - utils._each(config, adapterConfig => { + _each(config, adapterConfig => { var adapter = _analyticsRegistry[adapterConfig.provider].adapter; if (adapter) { adapter.enableAnalytics(adapterConfig); } else { - utils.logError(`Prebid Error: no analytics adapter found in registry for + logError(`Prebid Error: no analytics adapter found in registry for ${adapterConfig.provider}.`); } }); @@ -581,22 +618,22 @@ function tryCallBidderMethod(bidder, method, param) { const adapter = _bidderRegistry[bidder]; const spec = adapter.getSpec(); if (spec && spec[method] && typeof spec[method] === 'function') { - utils.logInfo(`Invoking ${bidder}.${method}`); + logInfo(`Invoking ${bidder}.${method}`); config.runWithBidder(bidder, bind.call(spec[method], spec, param)); } } catch (e) { - utils.logWarn(`Error calling ${method} of ${bidder}`); + logWarn(`Error calling ${method} of ${bidder}`); } } adapterManager.callTimedOutBidders = function(adUnits, timedOutBidders, cbTimeout) { timedOutBidders = timedOutBidders.map((timedOutBidder) => { // Adding user configured params & timeout to timeout event data - timedOutBidder.params = utils.getUserConfiguredParams(adUnits, timedOutBidder.adUnitCode, timedOutBidder.bidder); + timedOutBidder.params = getUserConfiguredParams(adUnits, timedOutBidder.adUnitCode, timedOutBidder.bidder); timedOutBidder.timeout = cbTimeout; return timedOutBidder; }); - timedOutBidders = utils.groupBy(timedOutBidders, 'bidder'); + timedOutBidders = groupBy(timedOutBidders, 'bidder'); Object.keys(timedOutBidders).forEach((bidder) => { tryCallBidderMethod(bidder, 'onTimeout', timedOutBidders[bidder]); @@ -605,7 +642,7 @@ adapterManager.callTimedOutBidders = function(adUnits, timedOutBidders, cbTimeou adapterManager.callBidWonBidder = function(bidder, bid, adUnits) { // Adding user configured params to bidWon event data - bid.params = utils.getUserConfiguredParams(adUnits, bid.adUnitCode, bid.bidder); + bid.params = getUserConfiguredParams(adUnits, bid.adUnitCode, bid.bidder); adunitCounter.incrementBidderWinsCounter(bid.adUnitCode, bid.bidder); tryCallBidderMethod(bidder, 'onBidWon', bid); }; @@ -618,4 +655,9 @@ adapterManager.callBidViewableBidder = function(bidder, bid) { tryCallBidderMethod(bidder, 'onBidViewable', bid); }; +adapterManager.callBidderError = function(bidder, error, bidderRequest) { + const param = { error, bidderRequest }; + tryCallBidderMethod(bidder, 'onBidderError', param); +}; + export default adapterManager; diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index c71c4ee355b..8030cde8bae 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -9,10 +9,11 @@ import CONSTANTS from '../constants.json'; import events from '../events.js'; import includes from 'core-js-pure/features/array/includes.js'; import { ajax } from '../ajax.js'; -import { logWarn, logError, parseQueryStringParameters, delayExecution, parseSizesInput, getBidderRequest, flatten, uniques, timestamp, deepAccess, isArray, isPlainObject } from '../utils.js'; +import { logWarn, logError, parseQueryStringParameters, delayExecution, parseSizesInput, flatten, uniques, timestamp, deepAccess, isArray, isPlainObject } from '../utils.js'; import { ADPOD } from '../mediaTypes.js'; import { getHook, hook } from '../hook.js'; import { getCoreStorageManager } from '../storageManager.js'; +import {auctionManager} from '../auctionManager.js'; export const storage = getCoreStorageManager('bidderFactory'); @@ -36,6 +37,7 @@ export const storage = getCoreStorageManager('bidderFactory'); * }); * * @see BidderSpec for the full API and more thorough descriptions. + * */ /** @@ -130,7 +132,7 @@ export const storage = getCoreStorageManager('bidderFactory'); */ // common params for all mediaTypes -const COMMON_BID_RESPONSE_KEYS = ['requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency']; +const COMMON_BID_RESPONSE_KEYS = ['cpm', 'ttl', 'creativeId', 'netRevenue', 'currency']; const DEFAULT_REFRESHIN_DAYS = 1; @@ -187,7 +189,7 @@ export function newBidder(spec) { const adUnitCodesHandled = {}; function addBidWithCode(adUnitCode, bid) { adUnitCodesHandled[adUnitCode] = true; - if (isValid(adUnitCode, bid, [bidderRequest])) { + if (isValid(adUnitCode, bid)) { addBidResponse(adUnitCode, bid); } } @@ -217,130 +219,34 @@ export function newBidder(spec) { } }); - let requests = spec.buildRequests(validBidRequests, bidderRequest); - if (!requests || requests.length === 0) { - afterAllResponses(); - return; - } - if (!Array.isArray(requests)) { - requests = [requests]; - } - - // Callbacks don't compose as nicely as Promises. We should call done() once _all_ the - // Server requests have returned and been processed. Since `ajax` accepts a single callback, - // we need to rig up a function which only executes after all the requests have been responded. - const onResponse = delayExecution(configEnabledCallback(afterAllResponses), requests.length) - requests.forEach(processRequest); - - function formatGetParameters(data) { - if (data) { - return `?${typeof data === 'object' ? parseQueryStringParameters(data) : data}`; - } - - return ''; - } - - function processRequest(request) { - switch (request.method) { - case 'GET': - ajax( - `${request.url}${formatGetParameters(request.data)}`, - { - success: configEnabledCallback(onSuccess), - error: onFailure - }, - undefined, - Object.assign({ - method: 'GET', - withCredentials: true - }, request.options) - ); - break; - case 'POST': - ajax( - request.url, - { - success: configEnabledCallback(onSuccess), - error: onFailure - }, - typeof request.data === 'string' ? request.data : JSON.stringify(request.data), - Object.assign({ - method: 'POST', - contentType: 'text/plain', - withCredentials: true - }, request.options) - ); - break; - default: - logWarn(`Skipping invalid request from ${spec.code}. Request type ${request.type} must be GET or POST`); - onResponse(); - } - - // If the server responds successfully, use the adapter code to unpack the Bids from it. - // If the adapter code fails, no bids should be added. After all the bids have been added, make - // sure to call the `onResponse` function so that we're one step closer to calling done(). - function onSuccess(response, responseObj) { + processBidderRequests(spec, validBidRequests, bidderRequest, ajax, configEnabledCallback, { + onRequest: requestObject => events.emit(CONSTANTS.EVENTS.BEFORE_BIDDER_HTTP, bidderRequest, requestObject), + onResponse: (resp) => { onTimelyResponse(spec.code); - - try { - response = JSON.parse(response); - } catch (e) { /* response might not be JSON... that's ok. */ } - - // Make response headers available for #1742. These are lazy-loaded because most adapters won't need them. - response = { - body: response, - headers: headerParser(responseObj) - }; - responses.push(response); - - let bids; - try { - bids = spec.interpretResponse(response, request); - } catch (err) { - logError(`Bidder ${spec.code} failed to interpret the server's response. Continuing without bids`, null, err); - onResponse(); - return; - } - - if (bids) { - if (isArray(bids)) { - bids.forEach(addBidUsingRequestMap); - } else { - addBidUsingRequestMap(bids); - } - } - onResponse(bids); - - function addBidUsingRequestMap(bid) { - const bidRequest = bidRequestMap[bid.requestId]; - if (bidRequest) { - // creating a copy of original values as cpm and currency are modified later - bid.originalCpm = bid.cpm; - bid.originalCurrency = bid.currency; - bid.meta = bid.meta || Object.assign({}, bid[bidRequest.bidder]); - const prebidBid = Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid); - addBidWithCode(bidRequest.adUnitCode, prebidBid); - } else { - logWarn(`Bidder ${spec.code} made bid for unknown request ID: ${bid.requestId}. Ignoring.`); - } - } - - function headerParser(xmlHttpResponse) { - return { - get: responseObj.getResponseHeader.bind(responseObj) - }; - } - } - - // If the server responds with an error, there's not much we can do. Log it, and make sure to - // call onResponse() so that we're one step closer to calling done(). - function onFailure(err) { + responses.push(resp) + }, + // If the server responds with an error, there's not much we can do beside logging. + onError: (errorMessage, error) => { onTimelyResponse(spec.code); - - logError(`Server call for ${spec.code} failed: ${err}. Continuing without bids.`); - onResponse(); - } - } + adapterManager.callBidderError(spec.code, error, bidderRequest) + events.emit(CONSTANTS.EVENTS.BIDDER_ERROR, { error, bidderRequest }); + logError(`Server call for ${spec.code} failed: ${errorMessage} ${error.status}. Continuing without bids.`); + }, + onBid: (bid) => { + const bidRequest = bidRequestMap[bid.requestId]; + if (bidRequest) { + // creating a copy of original values as cpm and currency are modified later + bid.originalCpm = bid.cpm; + bid.originalCurrency = bid.currency; + bid.meta = bid.meta || Object.assign({}, bid[bidRequest.bidder]); + const prebidBid = Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid); + addBidWithCode(bidRequest.adUnitCode, prebidBid); + } else { + logWarn(`Bidder ${spec.code} made bid for unknown request ID: ${bid.requestId}. Ignoring.`); + } + }, + onCompletion: afterAllResponses, + }); } }); @@ -357,6 +263,125 @@ export function newBidder(spec) { } } +/** + * Run a set of bid requests - that entails converting them to HTTP requests, sending + * them over the network, and parsing the responses. + * + * @param spec bid adapter spec + * @param bids bid requests to run + * @param bidderRequest the bid request object that `bids` is connected to + * @param ajax ajax method to use + * @param wrapCallback {function(callback)} a function used to wrap every callback (for the purpose of `config.currentBidder`) + * @param onRequest {function({})} invoked once for each HTTP request built by the adapter - with the raw request + * @param onResponse {function({})} invoked once on each successful HTTP response - with the raw response + * @param onError {function(String, {})} invoked once for each HTTP error - with status code and response + * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse + * @param onCompletion {function()} invoked once when all bid requests have been processed + */ +export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onError, onBid, onCompletion}) { + let requests = spec.buildRequests(bids, bidderRequest); + if (!requests || requests.length === 0) { + onCompletion(); + return; + } + if (!Array.isArray(requests)) { + requests = [requests]; + } + + const requestDone = delayExecution(onCompletion, requests.length); + + requests.forEach((request) => { + // If the server responds successfully, use the adapter code to unpack the Bids from it. + // If the adapter code fails, no bids should be added. After all the bids have been added, + // make sure to call the `requestDone` function so that we're one step closer to calling onCompletion(). + const onSuccess = wrapCallback(function(response, responseObj) { + try { + response = JSON.parse(response); + } catch (e) { /* response might not be JSON... that's ok. */ } + + // Make response headers available for #1742. These are lazy-loaded because most adapters won't need them. + response = { + body: response, + headers: headerParser(responseObj) + }; + onResponse(response); + + let bids; + try { + bids = spec.interpretResponse(response, request); + } catch (err) { + logError(`Bidder ${spec.code} failed to interpret the server's response. Continuing without bids`, null, err); + requestDone(); + return; + } + + if (bids) { + if (isArray(bids)) { + bids.forEach(onBid); + } else { + onBid(bids); + } + } + requestDone(); + + function headerParser(xmlHttpResponse) { + return { + get: responseObj.getResponseHeader.bind(responseObj) + }; + } + }); + + const onFailure = wrapCallback(function (errorMessage, error) { + onError(errorMessage, error); + requestDone(); + }); + + onRequest(request); + switch (request.method) { + case 'GET': + ajax( + `${request.url}${formatGetParameters(request.data)}`, + { + success: onSuccess, + error: onFailure + }, + undefined, + Object.assign({ + method: 'GET', + withCredentials: true + }, request.options) + ); + break; + case 'POST': + ajax( + request.url, + { + success: onSuccess, + error: onFailure + }, + typeof request.data === 'string' ? request.data : JSON.stringify(request.data), + Object.assign({ + method: 'POST', + contentType: 'text/plain', + withCredentials: true + }, request.options) + ); + break; + default: + logWarn(`Skipping invalid request from ${spec.code}. Request type ${request.type} must be GET or POST`); + requestDone(); + } + + function formatGetParameters(data) { + if (data) { + return `?${typeof data === 'object' ? parseQueryStringParameters(data) : data}`; + } + + return ''; + } + }) +}, 'processBidderRequests') + export const registerSyncInner = hook('async', function(spec, responses, gdprConsent, uspConsent) { const aliasSyncEnabled = config.getConfig('userSync.aliasSyncEnabled'); if (spec.getUserSyncs && (aliasSyncEnabled || !adapterManager.aliasRegistry[spec.code])) { @@ -449,16 +474,17 @@ export function getIabSubCategory(bidderCode, category) { } // check that the bid has a width and height set -function validBidSize(adUnitCode, bid, bidRequests) { +function validBidSize(adUnitCode, bid, {index = auctionManager.index} = {}) { if ((bid.width || parseInt(bid.width, 10) === 0) && (bid.height || parseInt(bid.height, 10) === 0)) { bid.width = parseInt(bid.width, 10); bid.height = parseInt(bid.height, 10); return true; } - const adUnit = getBidderRequest(bidRequests, bid.bidderCode, adUnitCode); + const bidRequest = index.getBidRequest(bid); + const mediaTypes = index.getMediaTypes(bid); - const sizes = adUnit && adUnit.bids && adUnit.bids[0] && adUnit.bids[0].sizes; + const sizes = (bidRequest && bidRequest.sizes) || (mediaTypes && mediaTypes.banner && mediaTypes.banner.sizes); const parsedSizes = parseSizesInput(sizes); // if a banner impression has one valid size, we assign that size to any bid @@ -474,7 +500,7 @@ function validBidSize(adUnitCode, bid, bidRequests) { } // Validate the arguments sent to us by the adapter. If this returns false, the bid should be totally ignored. -export function isValid(adUnitCode, bid, bidRequests) { +export function isValid(adUnitCode, bid, {index = auctionManager.index} = {}) { function hasValidKeys() { let bidKeys = Object.keys(bid); return COMMON_BID_RESPONSE_KEYS.every(key => includes(bidKeys, key) && !includes([undefined, null], bid[key])); @@ -499,15 +525,15 @@ export function isValid(adUnitCode, bid, bidRequests) { return false; } - if (bid.mediaType === 'native' && !nativeBidIsValid(bid, bidRequests)) { + if (bid.mediaType === 'native' && !nativeBidIsValid(bid, {index})) { logError(errorMessage('Native bid missing some required properties.')); return false; } - if (bid.mediaType === 'video' && !isValidVideoBid(bid, bidRequests)) { + if (bid.mediaType === 'video' && !isValidVideoBid(bid, {index})) { logError(errorMessage(`Video bid does not have required vastUrl or renderer property`)); return false; } - if (bid.mediaType === 'banner' && !validBidSize(adUnitCode, bid, bidRequests)) { + if (bid.mediaType === 'banner' && !validBidSize(adUnitCode, bid, {index})) { logError(errorMessage(`Banner bids require a width and height`)); return false; } diff --git a/src/adloader.js b/src/adloader.js index 5460cc79410..480c855821c 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -1,5 +1,5 @@ import includes from 'core-js-pure/features/array/includes.js'; -import * as utils from './utils.js'; +import { logError, logWarn, insertElement } from './utils.js'; const _requestCache = {}; // The below list contains modules or vendors whom Prebid allows to load external JS. @@ -8,7 +8,9 @@ const _approvedLoadExternalJSList = [ 'criteo', 'outstream', 'adagio', - 'browsi' + 'browsi', + 'brandmetrics', + 'justtag' ] /** @@ -20,11 +22,11 @@ const _approvedLoadExternalJSList = [ */ export function loadExternalScript(url, moduleCode, callback) { if (!moduleCode || !url) { - utils.logError('cannot load external script without url and moduleCode'); + logError('cannot load external script without url and moduleCode'); return; } if (!includes(_approvedLoadExternalJSList, moduleCode)) { - utils.logError(`${moduleCode} not whitelisted for loading external JavaScript`); + logError(`${moduleCode} not whitelisted for loading external JavaScript`); return; } // only load each asset once @@ -49,7 +51,7 @@ export function loadExternalScript(url, moduleCode, callback) { _requestCache[url].callbacks.push(callback); } - utils.logWarn(`module ${moduleCode} is loading external JavaScript`); + logWarn(`module ${moduleCode} is loading external JavaScript`); return requestResource(url, function () { _requestCache[url].loaded = true; try { @@ -57,7 +59,7 @@ export function loadExternalScript(url, moduleCode, callback) { _requestCache[url].callbacks[i](); } } catch (e) { - utils.logError('Error executing callback', 'adloader.js:loadExternalScript', e); + logError('Error executing callback', 'adloader.js:loadExternalScript', e); } }); @@ -84,7 +86,7 @@ export function loadExternalScript(url, moduleCode, callback) { jptScript.src = tagSrc; // add the new script tag to the page - utils.insertElement(jptScript); + insertElement(jptScript); return jptScript; } diff --git a/src/ajax.js b/src/ajax.js index b1e4cbdbdff..5e926f3210d 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -1,6 +1,5 @@ import { config } from './config.js'; - -var utils = require('./utils.js'); +import { logMessage, logError, parseUrl, buildUrl, _each } from './utils.js'; const XHR_DONE = 4; @@ -25,10 +24,10 @@ export function ajaxBuilder(timeout = 3000, {request, done} = {}) { let callbacks = typeof callback === 'object' && callback !== null ? callback : { success: function() { - utils.logMessage('xhr success'); + logMessage('xhr success'); }, error: function(e) { - utils.logError('xhr error', null, e); + logError('xhr error', null, e); } }; @@ -55,18 +54,18 @@ export function ajaxBuilder(timeout = 3000, {request, done} = {}) { // Disabled timeout temporarily to avoid xhr failed requests. https://github.com/prebid/Prebid.js/issues/2648 if (!config.getConfig('disableAjaxTimeout')) { x.ontimeout = function () { - utils.logError(' xhr timeout after ', x.timeout, 'ms'); + logError(' xhr timeout after ', x.timeout, 'ms'); }; } if (method === 'GET' && data) { - let urlInfo = utils.parseUrl(url, options); + let urlInfo = parseUrl(url, options); Object.assign(urlInfo.search, data); - url = utils.buildUrl(urlInfo); + url = buildUrl(urlInfo); } x.open(method, url, true); - // IE needs timoeut to be set after open - see #1410 + // IE needs timeout to be set after open - see #1410 // Disabled timeout temporarily to avoid xhr failed requests. https://github.com/prebid/Prebid.js/issues/2648 if (!config.getConfig('disableAjaxTimeout')) { x.timeout = timeout; @@ -75,7 +74,7 @@ export function ajaxBuilder(timeout = 3000, {request, done} = {}) { if (options.withCredentials) { x.withCredentials = true; } - utils._each(options.customHeaders, (value, header) => { + _each(options.customHeaders, (value, header) => { x.setRequestHeader(header, value); }); if (options.preflight) { @@ -93,7 +92,8 @@ export function ajaxBuilder(timeout = 3000, {request, done} = {}) { x.send(); } } catch (error) { - utils.logError('xhr construction', error); + logError('xhr construction', error); + typeof callback === 'object' && callback !== null && callback.error(error); } } } diff --git a/src/auction.js b/src/auction.js index a6f0342e582..f6bb00ca0f6 100644 --- a/src/auction.js +++ b/src/auction.js @@ -57,7 +57,10 @@ * @property {function(): void} callBids - sends requests to all adapters for bids */ -import {flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue, parseUrl} from './utils.js'; +import { + flatten, timestamp, adUnitsFilter, deepAccess, getValue, parseUrl, generateUUID, + logMessage, bind, logError, logInfo, logWarn, isEmpty, _each, isFn, isEmptyStr +} from './utils.js'; import { getPriceBucketString } from './cpmBucketManager.js'; import { getNativeTargeting } from './native.js'; import { getCacheUrl, store } from './videoCache.js'; @@ -69,9 +72,11 @@ import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; import { OUTSTREAM } from './video.js'; import { VIDEO } from './mediaTypes.js'; +import {auctionManager} from './auctionManager.js'; +import {bidderSettings} from './bidderSettings.js'; const { syncUsers } = userSync; -const utils = require('./utils.js'); + const adapterManager = require('./adapterManager.js').default; const events = require('./events.js'); const CONSTANTS = require('./constants.json'); @@ -112,7 +117,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a let _noBids = []; let _auctionStart; let _auctionEnd; - let _auctionId = auctionId || utils.generateUUID(); + let _auctionId = auctionId || generateUUID(); let _auctionStatus; let _callback = callback; let _timer; @@ -157,7 +162,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a if (_auctionEnd === undefined) { let timedOutBidders = []; if (timedOut) { - utils.logMessage(`Auction ${_auctionId} timedOut`); + logMessage(`Auction ${_auctionId} timedOut`); timedOutBidders = getTimedOutBids(_bidderRequests, _timelyBidders); if (timedOutBidders.length) { events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, timedOutBidders); @@ -173,13 +178,13 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a if (_callback != null) { const adUnitCodes = _adUnitCodes; const bids = _bidsReceived - .filter(utils.bind.call(adUnitsFilter, this, adUnitCodes)) + .filter(bind.call(adUnitsFilter, this, adUnitCodes)) .reduce(groupByPlacement, {}); _callback.apply($$PREBID_GLOBAL$$, [bids, timedOut, _auctionId]); _callback = null; } } catch (e) { - utils.logError('Error executing bidsBackHandler', null, e); + logError('Error executing bidsBackHandler', null, e); } finally { // Calling timed out bidders if (timedOutBidders.length) { @@ -199,7 +204,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a function auctionDone() { config.resetBidder(); // when all bidders have called done callback atleast once it means auction is complete - utils.logInfo(`Bids Received for Auction with id: ${_auctionId}`, _bidsReceived); + logInfo(`Bids Received for Auction with id: ${_auctionId}`, _bidsReceived); _auctionStatus = AUCTION_COMPLETED; executeCallback(false, true); } @@ -213,10 +218,10 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a _auctionStart = Date.now(); let bidRequests = adapterManager.makeBidRequests(_adUnits, _auctionStart, _auctionId, _timeout, _labels); - utils.logInfo(`Bids Requested for Auction with id: ${_auctionId}`, bidRequests); + logInfo(`Bids Requested for Auction with id: ${_auctionId}`, bidRequests); if (bidRequests.length < 1) { - utils.logWarn('No valid bid requests returned for auction'); + logWarn('No valid bid requests returned for auction'); auctionDone(); } else { addBidderRequests.call({ @@ -246,12 +251,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a events.emit(CONSTANTS.EVENTS.AUCTION_INIT, getProperties()); let callbacks = auctionCallbacks(auctionDone, this); - adapterManager.callBids(_adUnits, bidRequests, function(...args) { - addBidResponse.apply({ - dispatch: callbacks.addBidResponse, - bidderRequest: this - }, args) - }, callbacks.adapterDone, { + adapterManager.callBids(_adUnits, bidRequests, callbacks.addBidResponse, callbacks.adapterDone, { request(source, origin) { increment(outstandingRequests, origin); increment(requests, source); @@ -279,7 +279,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a }; if (!runIfOriginHasCapacity(call)) { - utils.logWarn('queueing auction due to limited endpoint capacity'); + logWarn('queueing auction due to limited endpoint capacity'); queuedCalls.push(call); } @@ -341,6 +341,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a addWinningBid, setBidTargeting, getWinningBids: () => _winningBids, + getAuctionStart: () => _auctionStart, getTimeout: () => _timeout, getAuctionId: () => _auctionId, getAuctionStatus: () => _auctionStatus, @@ -348,12 +349,13 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a getAdUnitCodes: () => _adUnitCodes, getBidRequests: () => _bidderRequests, getBidsReceived: () => _bidsReceived, - getNoBids: () => _noBids + getNoBids: () => _noBids, + } } -export const addBidResponse = hook('async', function(adUnitCode, bid) { - this.dispatch.call(this.bidderRequest, adUnitCode, bid); +export const addBidResponse = hook('sync', function(adUnitCode, bid) { + this.dispatch.call(null, adUnitCode, bid); }, 'addBidResponse'); export const addBidderRequests = hook('sync', function(bidderRequests) { @@ -366,11 +368,37 @@ export const bidsBackCallback = hook('async', function (adUnits, callback) { } }, 'bidsBackCallback'); -export function auctionCallbacks(auctionDone, auctionInstance) { +export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionManager.index} = {}) { let outstandingBidsAdded = 0; let allAdapterCalledDone = false; let bidderRequestsDone = new Set(); let bidResponseMap = {}; + const ready = {}; + + function waitFor(requestId, result) { + if (ready[requestId] == null) { + ready[requestId] = Promise.resolve(); + } + ready[requestId] = ready[requestId].then(() => Promise.resolve(result).catch(() => {})) + } + + function guard(bidderRequest, fn) { + let timeout = bidderRequest.timeout; + if (timeout == null || timeout > auctionInstance.getTimeout()) { + timeout = auctionInstance.getTimeout(); + } + const timeRemaining = auctionInstance.getAuctionStart() + timeout - Date.now(); + const wait = ready[bidderRequest.bidderRequestId]; + const orphanWait = ready['']; // also wait for "orphan" responses that are not associated with any request + if ((wait != null || orphanWait != null) && timeRemaining > 0) { + Promise.race([ + new Promise((resolve) => setTimeout(resolve, timeRemaining)), + Promise.resolve(orphanWait).then(() => wait) + ]).then(fn); + } else { + fn(); + } + } function afterBidAdded() { outstandingBidsAdded--; @@ -379,18 +407,16 @@ export function auctionCallbacks(auctionDone, auctionInstance) { } } - function addBidResponse(adUnitCode, bid) { - let bidderRequest = this; - + function handleBidResponse(adUnitCode, bid) { bidResponseMap[bid.requestId] = true; outstandingBidsAdded++; let auctionId = auctionInstance.getAuctionId(); - let bidResponse = getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}); + let bidResponse = getPreparedBidForAuction({adUnitCode, bid, auctionId}); if (bidResponse.mediaType === 'video') { - tryAddVideoBid(auctionInstance, bidResponse, bidderRequest, afterBidAdded); + tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded); } else { addBidToAuction(auctionInstance, bidResponse); afterBidAdded(); @@ -404,7 +430,7 @@ export function auctionCallbacks(auctionDone, auctionInstance) { bidderRequestsDone.add(bidderRequest); - if (auctionOptionsConfig && !utils.isEmpty(auctionOptionsConfig)) { + if (auctionOptionsConfig && !isEmpty(auctionOptionsConfig)) { const secondaryBidders = auctionOptionsConfig.secondaryBidders; if (secondaryBidders && !bidderRequests.every(bidder => includes(secondaryBidders, bidder.bidderCode))) { bidderRequests = bidderRequests.filter(request => !includes(secondaryBidders, request.bidderCode)); @@ -426,8 +452,15 @@ export function auctionCallbacks(auctionDone, auctionInstance) { } return { - addBidResponse, - adapterDone + addBidResponse: function (adUnit, bid) { + const bidderRequest = index.getBidderRequest(bid); + waitFor((bidderRequest && bidderRequest.bidderRequestId) || '', addBidResponse.call({ + dispatch: handleBidResponse, + }, adUnit, bid)); + }, + adapterDone: function () { + guard(this, adapterDone.bind(this)) + } } } @@ -439,9 +472,7 @@ export function doCallbacksIfTimedout(auctionInstance, bidResponse) { // Add a bid to the auction. export function addBidToAuction(auctionInstance, bidResponse) { - let bidderRequests = auctionInstance.getBidRequests(); - let bidderRequest = find(bidderRequests, bidderRequest => bidderRequest.bidderCode === bidResponse.bidderCode); - setupBidTargeting(bidResponse, bidderRequest); + setupBidTargeting(bidResponse); events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bidResponse); auctionInstance.addBidReceived(bidResponse); @@ -450,20 +481,22 @@ export function addBidToAuction(auctionInstance, bidResponse) { } // Video bids may fail if the cache is down, or there's trouble on the network. -function tryAddVideoBid(auctionInstance, bidResponse, bidRequests, afterBidAdded) { +function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = auctionManager.index} = {}) { let addBid = true; - const bidderRequest = getBidRequest(bidResponse.originalRequestId || bidResponse.requestId, [bidRequests]); - const videoMediaType = - bidderRequest && deepAccess(bidderRequest, 'mediaTypes.video'); + const videoMediaType = deepAccess( + index.getMediaTypes({ + requestId: bidResponse.originalRequestId || bidResponse.requestId, + transactionId: bidResponse.transactionId + }), 'video'); const context = videoMediaType && deepAccess(videoMediaType, 'context'); if (config.getConfig('cache.url') && context !== OUTSTREAM) { if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { addBid = false; - callPrebidCache(auctionInstance, bidResponse, afterBidAdded, bidderRequest); + callPrebidCache(auctionInstance, bidResponse, afterBidAdded, videoMediaType); } else if (!bidResponse.vastUrl) { - utils.logError('videoCacheKey specified but not required vastUrl for video bid'); + logError('videoCacheKey specified but not required vastUrl for video bid'); addBid = false; } } @@ -473,15 +506,15 @@ function tryAddVideoBid(auctionInstance, bidResponse, bidRequests, afterBidAdded } } -export const callPrebidCache = hook('async', function(auctionInstance, bidResponse, afterBidAdded, bidderRequest) { +export const callPrebidCache = hook('async', function(auctionInstance, bidResponse, afterBidAdded, videoMediaType) { store([bidResponse], function (error, cacheIds) { if (error) { - utils.logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); + logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); doCallbacksIfTimedout(auctionInstance, bidResponse); } else { if (cacheIds[0].uuid === '') { - utils.logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); + logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); doCallbacksIfTimedout(auctionInstance, bidResponse); } else { @@ -494,13 +527,14 @@ export const callPrebidCache = hook('async', function(auctionInstance, bidRespon afterBidAdded(); } } - }, bidderRequest); + }); }, 'callPrebidCache'); // Postprocess the bids so that all the universal properties exist, no matter which bidder they came from. // This should be called before addBidToAuction(). -function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { - const start = bidderRequest.start; +function getPreparedBidForAuction({adUnitCode, bid, auctionId}, {index = auctionManager.index} = {}) { + const bidderRequest = index.getBidderRequest(bid); + const start = (bidderRequest && bidderRequest.start) || bid.requestTimestamp; let bidObject = Object.assign({}, bid, { auctionId, @@ -520,14 +554,12 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bidObject); // a publisher-defined renderer can be used to render bids - const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode && bid.bidId == bidObject.requestId); - const adUnitRenderer = bidReq && bidReq.renderer; + const adUnitRenderer = index.getAdUnit(bidObject).renderer; // a publisher can also define a renderer for a mediaType const bidObjectMediaType = bidObject.mediaType; - const bidMediaType = bidReq && - bidReq.mediaTypes && - bidReq.mediaTypes[bidObjectMediaType]; + const mediaTypes = index.getMediaTypes(bidObject) + const bidMediaType = mediaTypes && mediaTypes[bidObjectMediaType]; var mediaTypeRenderer = bidMediaType && bidMediaType.renderer; @@ -546,7 +578,7 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { } // Use the config value 'mediaTypeGranularity' if it has been defined for mediaType, else use 'customPriceBucket' - const mediaTypeGranularity = getMediaTypeGranularity(bid.mediaType, bidReq, config.getConfig('mediaTypePriceGranularity')); + const mediaTypeGranularity = getMediaTypeGranularity(bid.mediaType, mediaTypes, config.getConfig('mediaTypePriceGranularity')); const priceStringsObj = getPriceBucketString( bidObject.cpm, (typeof mediaTypeGranularity === 'object') ? mediaTypeGranularity : config.getConfig('customPriceBucket'), @@ -562,11 +594,11 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { return bidObject; } -function setupBidTargeting(bidObject, bidderRequest) { +function setupBidTargeting(bidObject) { let keyValues; - if (bidObject.bidderCode && (bidObject.cpm > 0 || bidObject.dealId)) { - let bidReq = find(bidderRequest.bids, bid => bid.adUnitCode === bidObject.adUnitCode); - keyValues = getKeyValueTargetingPairs(bidObject.bidderCode, bidObject, bidReq); + const cpmCheck = (bidderSettings.get(bidObject.bidderCode, 'allowZeroCpmBids') === true) ? bidObject.cpm >= 0 : bidObject.cpm > 0; + if (bidObject.bidderCode && (cpmCheck || bidObject.dealId)) { + keyValues = getKeyValueTargetingPairs(bidObject.bidderCode, bidObject); } // use any targeting provided as defaults, otherwise just set from getKeyValueTargetingPairs @@ -575,14 +607,14 @@ function setupBidTargeting(bidObject, bidderRequest) { /** * @param {MediaType} mediaType - * @param {Bid} [bidReq] + * @param mediaTypes media types map from adUnit * @param {MediaTypePriceGranularity} [mediaTypePriceGranularity] * @returns {(Object|string|undefined)} */ -export function getMediaTypeGranularity(mediaType, bidReq, mediaTypePriceGranularity) { +export function getMediaTypeGranularity(mediaType, mediaTypes, mediaTypePriceGranularity) { if (mediaType && mediaTypePriceGranularity) { if (mediaType === VIDEO) { - const context = deepAccess(bidReq, `mediaTypes.${VIDEO}.context`, 'instream'); + const context = deepAccess(mediaTypes, `${VIDEO}.context`, 'instream'); if (mediaTypePriceGranularity[`${VIDEO}-${context}`]) { return mediaTypePriceGranularity[`${VIDEO}-${context}`]; } @@ -593,14 +625,14 @@ export function getMediaTypeGranularity(mediaType, bidReq, mediaTypePriceGranula /** * This function returns the price granularity defined. It can be either publisher defined or default value - * @param {string} mediaType - * @param {BidRequest} bidReq + * @param bid bid response object + * @param index * @returns {string} granularity */ -export const getPriceGranularity = (mediaType, bidReq) => { +export const getPriceGranularity = (bid, {index = auctionManager.index} = {}) => { // Use the config value 'mediaTypeGranularity' if it has been set for mediaType, else use 'priceGranularity' - const mediaTypeGranularity = getMediaTypeGranularity(mediaType, bidReq, config.getConfig('mediaTypePriceGranularity')); - const granularity = (typeof mediaType === 'string' && mediaTypeGranularity) ? ((typeof mediaTypeGranularity === 'string') ? mediaTypeGranularity : 'custom') : config.getConfig('priceGranularity'); + const mediaTypeGranularity = getMediaTypeGranularity(bid.mediaType, index.getMediaTypes(bid), config.getConfig('mediaTypePriceGranularity')); + const granularity = (typeof bid.mediaType === 'string' && mediaTypeGranularity) ? ((typeof mediaTypeGranularity === 'string') ? mediaTypeGranularity : 'custom') : config.getConfig('priceGranularity'); return granularity; } @@ -611,17 +643,18 @@ export const getPriceGranularity = (mediaType, bidReq) => { */ export const getPriceByGranularity = (granularity) => { return (bid) => { - if (granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { + const bidGranularity = granularity || getPriceGranularity(bid); + if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { return bid.pbAg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { + } else if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { return bid.pbDg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { + } else if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { return bid.pbLg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { + } else if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { return bid.pbMg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { + } else if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { return bid.pbHg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) { + } else if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) { return bid.pbCg; } } @@ -637,48 +670,51 @@ export const getAdvertiserDomain = () => { } } +// factory for key value objs +function createKeyVal(key, value) { + return { + key, + val: (typeof value === 'function') + ? function (bidResponse, bidReq) { + return value(bidResponse, bidReq); + } + : function (bidResponse) { + return getValue(bidResponse, value); + } + }; +} + +function defaultAdserverTargeting() { + const TARGETING_KEYS = CONSTANTS.TARGETING_KEYS; + return [ + createKeyVal(TARGETING_KEYS.BIDDER, 'bidderCode'), + createKeyVal(TARGETING_KEYS.AD_ID, 'adId'), + createKeyVal(TARGETING_KEYS.PRICE_BUCKET, getPriceByGranularity()), + createKeyVal(TARGETING_KEYS.SIZE, 'size'), + createKeyVal(TARGETING_KEYS.DEAL, 'dealId'), + createKeyVal(TARGETING_KEYS.SOURCE, 'source'), + createKeyVal(TARGETING_KEYS.FORMAT, 'mediaType'), + createKeyVal(TARGETING_KEYS.ADOMAIN, getAdvertiserDomain()), + ] +} + /** * @param {string} mediaType * @param {string} bidderCode * @param {BidRequest} bidReq * @returns {*} */ -export function getStandardBidderSettings(mediaType, bidderCode, bidReq) { - // factory for key value objs - function createKeyVal(key, value) { - return { - key, - val: (typeof value === 'function') - ? function (bidResponse) { - return value(bidResponse); - } - : function (bidResponse) { - return getValue(bidResponse, value); - } - }; - } +export function getStandardBidderSettings(mediaType, bidderCode) { const TARGETING_KEYS = CONSTANTS.TARGETING_KEYS; - const granularity = getPriceGranularity(mediaType, bidReq); + const standardSettings = Object.assign({}, bidderSettings.settingsFor(null)); - let bidderSettings = $$PREBID_GLOBAL$$.bidderSettings; - if (!bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]) { - bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] = {}; - } - if (!bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { - bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = [ - createKeyVal(TARGETING_KEYS.BIDDER, 'bidderCode'), - createKeyVal(TARGETING_KEYS.AD_ID, 'adId'), - createKeyVal(TARGETING_KEYS.PRICE_BUCKET, getPriceByGranularity(granularity)), - createKeyVal(TARGETING_KEYS.SIZE, 'size'), - createKeyVal(TARGETING_KEYS.DEAL, 'dealId'), - createKeyVal(TARGETING_KEYS.SOURCE, 'source'), - createKeyVal(TARGETING_KEYS.FORMAT, 'mediaType'), - createKeyVal(TARGETING_KEYS.ADOMAIN, getAdvertiserDomain()), - ] + if (!standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { + standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = defaultAdserverTargeting(); } if (mediaType === 'video') { - const adserverTargeting = bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]; + const adserverTargeting = standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].slice(); + standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = adserverTargeting; // Adding hb_uuid + hb_cache_id [TARGETING_KEYS.UUID, TARGETING_KEYS.CACHE_ID].forEach(targetingKeyVal => { @@ -688,66 +724,63 @@ export function getStandardBidderSettings(mediaType, bidderCode, bidReq) { }); // Adding hb_cache_host - if (config.getConfig('cache.url') && (!bidderCode || utils.deepAccess(bidderSettings, `${bidderCode}.sendStandardTargeting`) !== false)) { + if (config.getConfig('cache.url') && (!bidderCode || bidderSettings.get(bidderCode, 'sendStandardTargeting') !== false)) { const urlInfo = parseUrl(config.getConfig('cache.url')); if (typeof find(adserverTargeting, targetingKeyVal => targetingKeyVal.key === TARGETING_KEYS.CACHE_HOST) === 'undefined') { adserverTargeting.push(createKeyVal(TARGETING_KEYS.CACHE_HOST, function(bidResponse) { - return utils.deepAccess(bidResponse, `adserverTargeting.${TARGETING_KEYS.CACHE_HOST}`) + return deepAccess(bidResponse, `adserverTargeting.${TARGETING_KEYS.CACHE_HOST}`) ? bidResponse.adserverTargeting[TARGETING_KEYS.CACHE_HOST] : urlInfo.hostname; })); } } } - return bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]; + return standardSettings; } -export function getKeyValueTargetingPairs(bidderCode, custBidObj, bidReq) { +export function getKeyValueTargetingPairs(bidderCode, custBidObj, {index = auctionManager.index} = {}) { if (!custBidObj) { return {}; } - + const bidRequest = index.getBidRequest(custBidObj); var keyValues = {}; - var bidderSettings = $$PREBID_GLOBAL$$.bidderSettings; // 1) set the keys from "standard" setting or from prebid defaults - if (bidderSettings) { - // initialize default if not set - const standardSettings = getStandardBidderSettings(custBidObj.mediaType, bidderCode, bidReq); - setKeys(keyValues, standardSettings, custBidObj); - - // 2) set keys from specific bidder setting override if they exist - if (bidderCode && bidderSettings[bidderCode] && bidderSettings[bidderCode][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { - setKeys(keyValues, bidderSettings[bidderCode], custBidObj); - custBidObj.sendStandardTargeting = bidderSettings[bidderCode].sendStandardTargeting; - } + // initialize default if not set + const standardSettings = getStandardBidderSettings(custBidObj.mediaType, bidderCode); + setKeys(keyValues, standardSettings, custBidObj, bidRequest); + + // 2) set keys from specific bidder setting override if they exist + if (bidderCode && bidderSettings.getOwn(bidderCode, CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING)) { + setKeys(keyValues, bidderSettings.ownSettingsFor(bidderCode), custBidObj, bidRequest); + custBidObj.sendStandardTargeting = bidderSettings.get(bidderCode, 'sendStandardTargeting'); } // set native key value targeting if (custBidObj['native']) { - keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj, bidReq)); + keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj)); } return keyValues; } -function setKeys(keyValues, bidderSettings, custBidObj) { +function setKeys(keyValues, bidderSettings, custBidObj, bidReq) { var targeting = bidderSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]; custBidObj.size = custBidObj.getSize(); - utils._each(targeting, function (kvPair) { + _each(targeting, function (kvPair) { var key = kvPair.key; var value = kvPair.val; if (keyValues[key]) { - utils.logWarn('The key: ' + key + ' is getting ovewritten'); + logWarn('The key: ' + key + ' is being overwritten'); } - if (utils.isFn(value)) { + if (isFn(value)) { try { - value = value(custBidObj); + value = value(custBidObj, bidReq); } catch (e) { - utils.logError('bidmanager', 'ERROR', e); + logError('bidmanager', 'ERROR', e); } } @@ -755,12 +788,12 @@ function setKeys(keyValues, bidderSettings, custBidObj) { ((typeof bidderSettings.suppressEmptyKeys !== 'undefined' && bidderSettings.suppressEmptyKeys === true) || key === CONSTANTS.TARGETING_KEYS.DEAL) && // hb_deal is suppressed automatically if not set ( - utils.isEmptyStr(value) || + isEmptyStr(value) || value === null || value === undefined ) ) { - utils.logInfo("suppressing empty key '" + key + "' from adserver targeting"); + logInfo("suppressing empty key '" + key + "' from adserver targeting"); } else { keyValues[key] = value; } @@ -772,19 +805,13 @@ function setKeys(keyValues, bidderSettings, custBidObj) { export function adjustBids(bid) { let code = bid.bidderCode; let bidPriceAdjusted = bid.cpm; - let bidCpmAdjustment; - if ($$PREBID_GLOBAL$$.bidderSettings) { - if (code && $$PREBID_GLOBAL$$.bidderSettings[code] && typeof $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment === 'function') { - bidCpmAdjustment = $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment; - } else if ($$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] && typeof $$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment === 'function') { - bidCpmAdjustment = $$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment; - } - if (bidCpmAdjustment) { - try { - bidPriceAdjusted = bidCpmAdjustment(bid.cpm, Object.assign({}, bid)); - } catch (e) { - utils.logError('Error during bid adjustment', 'bidmanager.js', e); - } + const bidCpmAdjustment = bidderSettings.get(code || null, 'bidCpmAdjustment'); + + if (bidCpmAdjustment && typeof bidCpmAdjustment === 'function') { + try { + bidPriceAdjusted = bidCpmAdjustment(bid.cpm, Object.assign({}, bid)); + } catch (e) { + logError('Error during bid adjustment', 'bidmanager.js', e); } } diff --git a/src/auctionIndex.js b/src/auctionIndex.js new file mode 100644 index 00000000000..bdd2b42f9c6 --- /dev/null +++ b/src/auctionIndex.js @@ -0,0 +1,85 @@ +/** + * Retrieves request-related bid data. + * All methods are designed to work with Bid (response) objects returned by bid adapters. + */ +export function AuctionIndex(getAuctions) { + Object.assign(this, { + /** + * @param auctionId + * @returns {*} Auction instance for `auctionId` + */ + getAuction({auctionId}) { + if (auctionId != null) { + return getAuctions() + .find(auction => auction.getAuctionId() === auctionId); + } + }, + /** + * NOTE: you should prefer {@link #getMediaTypes} for looking up bid media types. + * @param transactionId + * @returns adUnit object for `transactionId` + */ + getAdUnit({transactionId}) { + if (transactionId != null) { + return getAuctions() + .flatMap(a => a.getAdUnits()) + .find(au => au.transactionId === transactionId); + } + }, + /** + * @param transactionId + * @param requestId? + * @returns {*} mediaTypes object from bidRequest (through requestId) falling back to the adUnit (through transactionId). + * + * The bidRequest is given precedence because its mediaTypes can differ from the adUnit's (if bidder-specific labels are in use). + * Bids that have no associated request do not have labels either, and use the adUnit's mediaTypes. + */ + getMediaTypes({transactionId, requestId}) { + if (requestId != null) { + const req = this.getBidRequest({requestId}); + if (req != null && (transactionId == null || req.transactionId === transactionId)) { + return req.mediaTypes; + } + } else if (transactionId != null) { + const au = this.getAdUnit({transactionId}); + if (au != null) { + return au.mediaTypes; + } + } + }, + /** + * @param requestId? + * @param bidderRequestId? + * @returns {*} bidderRequest that matches both requestId and bidderRequestId (if either or both are provided). + * + * NOTE: Bid responses are not guaranteed to have a corresponding request. + */ + getBidderRequest({requestId, bidderRequestId}) { + if (requestId != null || bidderRequestId != null) { + let bers = getAuctions().flatMap(a => a.getBidRequests()); + if (bidderRequestId != null) { + bers = bers.filter(ber => ber.bidderRequestId === bidderRequestId); + } + if (requestId == null) { + return bers[0]; + } else { + return bers.find(ber => ber.bids && ber.bids.find(br => br.bidId === requestId) != null) + } + } + }, + /** + * @param requestId + * @returns {*} bidRequest object for requestId + * + * NOTE: Bid responses are not guaranteed to have a corresponding request. + */ + getBidRequest({requestId}) { + if (requestId != null) { + return getAuctions() + .flatMap(a => a.getBidRequests()) + .flatMap(ber => ber.bids) + .find(br => br && br.bidId === requestId); + } + } + }); +} diff --git a/src/auctionManager.js b/src/auctionManager.js index 1aca1277aa8..089c0269d03 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -15,11 +15,14 @@ * @property {function(): Object} createAuction - creates auction instance and stores it for future reference * @property {function(): Object} findBidByAdId - find bid received by adId. This function will be called by $$PREBID_GLOBAL$$.renderAd * @property {function(): Object} getStandardBidderAdServerTargeting - returns standard bidder targeting for all the adapters. Refer http://prebid.org/dev-docs/publisher-api-reference.html#module_pbjs.bidderSettings for more details + * @property {function(Object): void} addWinningBid - add a winning bid to an auction based on auctionId + * @property {function(): void} clearAllAuctions - clear all auctions for testing */ import { uniques, flatten, logWarn } from './utils.js'; import { newAuction, getStandardBidderSettings, AUCTION_COMPLETED } from './auction.js'; import find from 'core-js-pure/features/array/find.js'; +import {AuctionIndex} from './auctionIndex.js'; const CONSTANTS = require('./constants.json'); @@ -113,10 +116,16 @@ export function newAuctionManager() { return _auctions.length && _auctions[_auctions.length - 1].getAuctionId() }; + auctionManager.clearAllAuctions = function() { + _auctions.length = 0; + } + function _addAuction(auction) { _auctions.push(auction); } + auctionManager.index = new AuctionIndex(() => _auctions); + return auctionManager; } diff --git a/src/bidderSettings.js b/src/bidderSettings.js new file mode 100644 index 00000000000..343d35a89d2 --- /dev/null +++ b/src/bidderSettings.js @@ -0,0 +1,69 @@ +import {deepAccess, mergeDeep} from './utils.js'; +import {getGlobal} from './prebidGlobal.js'; + +const CONSTANTS = require('./constants.json'); + +export class ScopedSettings { + constructor(getSettings, defaultScope) { + this.getSettings = getSettings; + this.defaultScope = defaultScope; + } + + /** + * Get setting value at `path` under the given scope, falling back to the default scope if needed. + * If `scope` is `null`, get the setting's default value. + * @param scope {String|null} + * @param path {String} + * @returns {*} + */ + get(scope, path) { + let value = this.getOwn(scope, path); + if (typeof value === 'undefined') { + value = this.getOwn(null, path); + } + return value; + } + + /** + * Get the setting value at `path` *without* falling back to the default value. + * @param scope {String} + * @param path {String} + * @returns {*} + */ + getOwn(scope, path) { + scope = this.#resolveScope(scope); + return deepAccess(this.getSettings(), `${scope}.${path}`) + } + + /** + * @returns {string[]} all existing scopes except the default one. + */ + getScopes() { + return Object.keys(this.getSettings()).filter((scope) => scope !== this.defaultScope); + } + + /** + * @returns all settings in the given scope, merged with the settings for the default scope. + */ + settingsFor(scope) { + return mergeDeep({}, this.ownSettingsFor(null), this.ownSettingsFor(scope)); + } + + /** + * @returns all settings in the given scope, *without* any of the default settings. + */ + ownSettingsFor(scope) { + scope = this.#resolveScope(scope); + return this.getSettings()[scope] || {}; + } + + #resolveScope(scope) { + if (scope == null) { + return this.defaultScope; + } else { + return scope; + } + } +} + +export const bidderSettings = new ScopedSettings(() => getGlobal().bidderSettings || {}, CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD); diff --git a/src/bidfactory.js b/src/bidfactory.js index 8701184c799..95d69cf0adb 100644 --- a/src/bidfactory.js +++ b/src/bidfactory.js @@ -1,4 +1,4 @@ -var utils = require('./utils.js'); +import { getUniqueIdentifierStr } from './utils.js'; /** Required paramaters @@ -14,16 +14,18 @@ var utils = require('./utils.js'); dealId, priceKeyString; */ -function Bid(statusCode, bidRequest) { - var _bidSrc = (bidRequest && bidRequest.src) || 'client'; +function Bid(statusCode, {src = 'client', bidder = '', bidId, transactionId, auctionId} = {}) { + var _bidSrc = src; var _statusCode = statusCode || 0; - this.bidderCode = (bidRequest && bidRequest.bidder) || ''; + this.bidderCode = bidder; this.width = 0; this.height = 0; this.statusMessage = _getStatus(); - this.adId = utils.getUniqueIdentifierStr(); - this.requestId = bidRequest && bidRequest.bidId; + this.adId = getUniqueIdentifierStr(); + this.requestId = bidId; + this.transactionId = transactionId; + this.auctionId = auctionId; this.mediaType = 'banner'; this.source = _bidSrc; @@ -48,9 +50,19 @@ function Bid(statusCode, bidRequest) { this.getSize = function () { return this.width + 'x' + this.height; }; + + this.getIdentifiers = function () { + return { + src: this.source, + bidder: this.bidderCode, + bidId: this.requestId, + transactionId: this.transactionId, + auctionId: this.auctionId + } + } } // Bid factory function. -export function createBid(statusCode, bidRequest) { - return new Bid(statusCode, bidRequest); +export function createBid(statusCode, identifiers) { + return new Bid(statusCode, identifiers); } diff --git a/src/config.js b/src/config.js index 7cb1a81955d..ddf3b79a6af 100644 --- a/src/config.js +++ b/src/config.js @@ -1,6 +1,6 @@ /* * Module for getting and setting Prebid configuration. - */ +*/ /** * @typedef {Object} MediaTypePriceGranularity @@ -16,13 +16,15 @@ import { isValidPriceConfig } from './cpmBucketManager.js'; import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; import Set from 'core-js-pure/features/set'; -import { mergeDeep } from './utils.js'; +import { + mergeDeep, deepClone, getParameterByName, isPlainObject, logMessage, logWarn, logError, + isArray, isStr, isBoolean, deepAccess, bind +} from './utils.js'; const from = require('core-js-pure/features/array/from.js'); -const utils = require('./utils.js'); const CONSTANTS = require('./constants.json'); -const DEFAULT_DEBUG = utils.getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; +const DEFAULT_DEBUG = getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; const DEFAULT_BIDDER_TIMEOUT = 3000; const DEFAULT_PUBLISHER_DOMAIN = window.location.origin; const DEFAULT_ENABLE_SEND_ALL_BIDS = true; @@ -103,10 +105,10 @@ export function newConfig() { if (validatePriceGranularity(val)) { if (typeof val === 'string') { this._priceGranularity = (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM; - } else if (utils.isPlainObject(val)) { + } else if (isPlainObject(val)) { this._customPriceBucket = val; this._priceGranularity = GRANULARITY_OPTIONS.CUSTOM; - utils.logMessage('Using custom price granularity'); + logMessage('Using custom price granularity'); } } }, @@ -133,12 +135,12 @@ export function newConfig() { if (validatePriceGranularity(val[item])) { if (typeof val === 'string') { aggregate[item] = (hasGranularity(val[item])) ? val[item] : this._priceGranularity; - } else if (utils.isPlainObject(val)) { + } else if (isPlainObject(val)) { aggregate[item] = val[item]; - utils.logMessage(`Using custom price granularity for ${item}`); + logMessage(`Using custom price granularity for ${item}`); } } else { - utils.logWarn(`Invalid price granularity for media type: ${item}`); + logWarn(`Invalid price granularity for media type: ${item}`); } return aggregate; }, {}); @@ -180,7 +182,7 @@ export function newConfig() { if (VALID_ORDERS[val]) { this._bidderSequence = val; } else { - utils.logWarn(`Invalid order: ${val}. Bidder Sequence was not set.`); + logWarn(`Invalid order: ${val}. Bidder Sequence was not set.`); } }, @@ -242,16 +244,16 @@ export function newConfig() { function validatePriceGranularity(val) { if (!val) { - utils.logError('Prebid Error: no value passed to `setPriceGranularity()`'); + logError('Prebid Error: no value passed to `setPriceGranularity()`'); return false; } if (typeof val === 'string') { if (!hasGranularity(val)) { - utils.logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.'); + logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.'); } - } else if (utils.isPlainObject(val)) { + } else if (isPlainObject(val)) { if (!isValidPriceConfig(val)) { - utils.logError('Invalid custom price value passed to `setPriceGranularity()`'); + logError('Invalid custom price value passed to `setPriceGranularity()`'); return false; } } @@ -259,24 +261,29 @@ export function newConfig() { } function validateauctionOptions(val) { - if (!utils.isPlainObject(val)) { - utils.logWarn('Auction Options must be an object') + if (!isPlainObject(val)) { + logWarn('Auction Options must be an object') return false } for (let k of Object.keys(val)) { - if (k !== 'secondaryBidders') { - utils.logWarn(`Auction Options given an incorrect param: ${k}`) + if (k !== 'secondaryBidders' && k !== 'suppressStaleRender') { + logWarn(`Auction Options given an incorrect param: ${k}`) return false } if (k === 'secondaryBidders') { - if (!utils.isArray(val[k])) { - utils.logWarn(`Auction Options ${k} must be of type Array`); + if (!isArray(val[k])) { + logWarn(`Auction Options ${k} must be of type Array`); return false - } else if (!val[k].every(utils.isStr)) { - utils.logWarn(`Auction Options ${k} must be only string`); + } else if (!val[k].every(isStr)) { + logWarn(`Auction Options ${k} must be only string`); return false } + } else if (k === 'suppressStaleRender') { + if (!isBoolean(val[k])) { + logWarn(`Auction Options ${k} must be of type boolean`); + return false; + } } } return true; @@ -288,7 +295,7 @@ export function newConfig() { * @private */ function _getConfig() { - if (currBidder && bidderConfig && utils.isPlainObject(bidderConfig[currBidder])) { + if (currBidder && bidderConfig && isPlainObject(bidderConfig[currBidder])) { let currBidderConfig = bidderConfig[currBidder]; const configTopicSet = new Set(Object.keys(config).concat(Object.keys(currBidderConfig))); @@ -298,7 +305,7 @@ export function newConfig() { } else if (typeof config[topic] === 'undefined') { memo[topic] = currBidderConfig[topic]; } else { - if (utils.isPlainObject(currBidderConfig[topic])) { + if (isPlainObject(currBidderConfig[topic])) { memo[topic] = mergeDeep({}, config[topic], currBidderConfig[topic]); } else { memo[topic] = currBidderConfig[topic]; @@ -310,6 +317,26 @@ export function newConfig() { return Object.assign({}, config); } + /* + * Returns the configuration object if called without parameters, + * or single configuration property if given a string matching a configuration + * property name. Allows deep access e.g. getConfig('currency.adServerCurrency') + * + * If called with callback parameter, or a string and a callback parameter, + * subscribes to configuration updates. See `subscribe` function for usage. + * + * The object returned is a deepClone of the `config` property. + */ + function readConfig(...args) { + if (args.length <= 1 && typeof args[0] !== 'function') { + const option = args[0]; + const configClone = deepClone(_getConfig()); + return option ? deepAccess(configClone, option) : configClone; + } + + return subscribe(...args); + } + /* * Returns configuration object if called without parameters, * or single configuration property if given a string matching a configuration @@ -321,7 +348,7 @@ export function newConfig() { function getConfig(...args) { if (args.length <= 1 && typeof args[0] !== 'function') { const option = args[0]; - return option ? utils.deepAccess(_getConfig(), option) : _getConfig(); + return option ? deepAccess(_getConfig(), option) : _getConfig(); } return subscribe(...args); @@ -346,9 +373,9 @@ export function newConfig() { let prop = (type === 'site') ? 'context' : type; duplicate[prop] = (prop === 'context' || prop === 'user') ? Object.keys(obj[type]).filter(key => key !== 'data').reduce((result, key) => { if (key === 'ext') { - utils.mergeDeep(result, obj[type][key]); + mergeDeep(result, obj[type][key]); } else { - utils.mergeDeep(result, {[key]: obj[type][key]}); + mergeDeep(result, {[key]: obj[type][key]}); } return result; @@ -366,14 +393,14 @@ export function newConfig() { let duplicate = {}; - if (utils.deepAccess(obj, 'ext.data')) { + if (deepAccess(obj, 'ext.data')) { Object.keys(obj.ext.data).forEach((key) => { if (key === 'pbadslot') { - utils.mergeDeep(duplicate, {context: {pbAdSlot: obj.ext.data[key]}}); + mergeDeep(duplicate, {context: {pbAdSlot: obj.ext.data[key]}}); } else if (key === 'adserver') { - utils.mergeDeep(duplicate, {context: {adServer: obj.ext.data[key]}}); + mergeDeep(duplicate, {context: {adServer: obj.ext.data[key]}}); } else { - utils.mergeDeep(duplicate, {context: {data: {[key]: obj.ext.data[key]}}}); + mergeDeep(duplicate, {context: {data: {[key]: obj.ext.data[key]}}}); } }); } @@ -391,9 +418,9 @@ export function newConfig() { let prop = (type === 'context') ? 'site' : type; duplicate[prop] = (prop === 'site' || prop === 'user') ? Object.keys(opt[type]).reduce((result, key) => { if (key === 'data') { - utils.mergeDeep(result, {ext: {data: opt[type][key]}}); + mergeDeep(result, {ext: {data: opt[type][key]}}); } else { - utils.mergeDeep(result, {[key]: opt[type][key]}); + mergeDeep(result, {[key]: opt[type][key]}); } return result; @@ -413,14 +440,14 @@ export function newConfig() { Object.keys(opt).filter(prop => prop === 'context').forEach((type) => { Object.keys(opt[type]).forEach((key) => { if (key === 'data') { - utils.mergeDeep(duplicate, {ext: {data: opt[type][key]}}); + mergeDeep(duplicate, {ext: {data: opt[type][key]}}); } else { if (typeof opt[type][key] === 'object' && !Array.isArray(opt[type][key])) { Object.keys(opt[type][key]).forEach(data => { - utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: {[data.toLowerCase()]: opt[type][key][data]}}}}); + mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: {[data.toLowerCase()]: opt[type][key][data]}}}}); }); } else { - utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: opt[type][key]}}}); + mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: opt[type][key]}}}); } } }); @@ -437,7 +464,7 @@ export function newConfig() { arr.forEach((adunit) => { if (adunit.fpd) { - (adunit['ortb2Imp']) ? utils.mergeDeep(adunit['ortb2Imp'], convertImpFpd(adunit.fpd)) : adunit['ortb2Imp'] = convertImpFpd(adunit.fpd); + (adunit['ortb2Imp']) ? mergeDeep(adunit['ortb2Imp'], convertImpFpd(adunit.fpd)) : adunit['ortb2Imp'] = convertImpFpd(adunit.fpd); convert.push((({ fpd, ...duplicate }) => duplicate)(adunit)); } else { convert.push(adunit); @@ -452,8 +479,8 @@ export function newConfig() { * listeners that were added by the `subscribe` function */ function setConfig(options) { - if (!utils.isPlainObject(options)) { - utils.logError('setConfig options must be an object'); + if (!isPlainObject(options)) { + logError('setConfig options must be an object'); return; } @@ -464,7 +491,7 @@ export function newConfig() { let prop = (topic === 'fpd') ? 'ortb2' : topic; let option = (topic === 'fpd') ? convertFpd(options[topic]) : options[topic]; - if (utils.isPlainObject(defaults[prop]) && utils.isPlainObject(option)) { + if (isPlainObject(defaults[prop]) && isPlainObject(option)) { option = Object.assign({}, defaults[prop], option); } @@ -479,8 +506,8 @@ export function newConfig() { * @param {object} options */ function setDefaults(options) { - if (!utils.isPlainObject(defaults)) { - utils.logError('defaults must be an object'); + if (!isPlainObject(defaults)) { + logError('defaults must be an object'); return; } @@ -521,7 +548,7 @@ export function newConfig() { } if (typeof callback !== 'function') { - utils.logError('listener must be a function'); + logError('listener must be a function'); return; } @@ -553,7 +580,7 @@ export function newConfig() { .forEach(listener => listener.callback(options)); } - function setBidderConfig(config) { + function setBidderConfig(config, mergeFlag = false) { try { check(config); config.bidders.forEach(bidder => { @@ -564,29 +591,51 @@ export function newConfig() { let prop = (topic === 'fpd') ? 'ortb2' : topic; let option = (topic === 'fpd') ? convertFpd(config.config[topic]) : config.config[topic]; - if (utils.isPlainObject(option)) { - bidderConfig[bidder][prop] = Object.assign({}, bidderConfig[bidder][prop] || {}, option); + if (isPlainObject(option)) { + const func = mergeFlag ? mergeDeep : Object.assign; + bidderConfig[bidder][prop] = func({}, bidderConfig[bidder][prop] || {}, option); } else { bidderConfig[bidder][prop] = option; } }); }); } catch (e) { - utils.logError(e); + logError(e); } + function check(obj) { - if (!utils.isPlainObject(obj)) { + if (!isPlainObject(obj)) { throw 'setBidderConfig bidder options must be an object'; } if (!(Array.isArray(obj.bidders) && obj.bidders.length)) { throw 'setBidderConfig bidder options must contain a bidders list with at least 1 bidder'; } - if (!utils.isPlainObject(obj.config)) { + if (!isPlainObject(obj.config)) { throw 'setBidderConfig bidder options must contain a config object'; } } } + function mergeConfig(obj) { + if (!isPlainObject(obj)) { + logError('mergeConfig input must be an object'); + return; + } + + const mergedConfig = Object.keys(obj).reduce((accum, key) => { + const prevConf = _getConfig(key)[key] || {}; + accum[key] = mergeDeep(prevConf, obj[key]); + return accum; + }, {}); + + setConfig({ ...mergedConfig }); + return mergedConfig; + } + + function mergeBidderConfig(obj) { + return setBidderConfig(obj, true); + } + /** * Internal functions for core to execute some synchronous code while having an active bidder set. */ @@ -602,9 +651,9 @@ export function newConfig() { return function(cb) { return function(...args) { if (typeof cb === 'function') { - return runWithBidder(bidder, utils.bind.call(cb, this, ...args)) + return runWithBidder(bidder, bind.call(cb, this, ...args)) } else { - utils.logWarn('config.callbackWithBidder callback is not a function'); + logWarn('config.callbackWithBidder callback is not a function'); } } } @@ -624,13 +673,16 @@ export function newConfig() { getCurrentBidder, resetBidder, getConfig, + readConfig, setConfig, + mergeConfig, setDefaults, resetConfig, runWithBidder, callbackWithBidder, setBidderConfig, getBidderConfig, + mergeBidderConfig, convertAdUnitFpd, getLegacyFpd, getLegacyImpFpd diff --git a/src/constants.json b/src/constants.json index e6b9687f911..055ccd8aacb 100644 --- a/src/constants.json +++ b/src/constants.json @@ -32,14 +32,18 @@ "NO_BID": "noBid", "BID_WON": "bidWon", "BIDDER_DONE": "bidderDone", + "BIDDER_ERROR": "bidderError", "SET_TARGETING": "setTargeting", "BEFORE_REQUEST_BIDS": "beforeRequestBids", + "BEFORE_BIDDER_HTTP": "beforeBidderHttp", "REQUEST_BIDS": "requestBids", "ADD_AD_UNITS": "addAdUnits", "AD_RENDER_FAILED": "adRenderFailed", + "AD_RENDER_SUCCEEDED": "adRenderSucceeded", "TCF2_ENFORCEMENT": "tcf2Enforcement", "AUCTION_DEBUG": "auctionDebug", - "BID_VIEWABLE": "bidViewable" + "BID_VIEWABLE": "bidViewable", + "STALE_RENDER": "staleRender" }, "AD_RENDER_FAILED_REASON" : { "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocument", @@ -78,10 +82,8 @@ "PRICE_BUCKET": "hb_pb", "SIZE": "hb_size", "DEAL": "hb_deal", - "SOURCE": "hb_source", "FORMAT": "hb_format", "UUID": "hb_uuid", - "CACHE_ID": "hb_cache_id", "CACHE_HOST": "hb_cache_host" }, "NATIVE_KEYS": { diff --git a/src/cpmBucketManager.js b/src/cpmBucketManager.js index a6b76cc38e2..b90dc8df717 100644 --- a/src/cpmBucketManager.js +++ b/src/cpmBucketManager.js @@ -1,5 +1,5 @@ import find from 'core-js-pure/features/array/find.js'; -const utils = require('./utils.js'); +import { isEmpty } from './utils.js'; const _defaultPrecision = 2; const _lgPriceConfig = { @@ -102,7 +102,7 @@ function getCpmStringValue(cpm, config, granularityMultiplier) { } function isValidPriceConfig(config) { - if (utils.isEmpty(config) || !config.buckets || !Array.isArray(config.buckets)) { + if (isEmpty(config) || !config.buckets || !Array.isArray(config.buckets)) { return false; } let isValid = true; diff --git a/src/debugging.js b/src/debugging.js index dc479f74674..810cf4b432a 100644 --- a/src/debugging.js +++ b/src/debugging.js @@ -1,20 +1,24 @@ +import {config} from './config.js'; +import {addBidderRequests, addBidResponse} from './auction.js'; +import {hook} from './hook.js'; +import {prefixLog} from './utils.js'; -import { config } from './config.js'; -import { logMessage as utilsLogMessage, logWarn as utilsLogWarn } from './utils.js'; -import { addBidderRequests, addBidResponse } from './auction.js'; +const {logWarn, logMessage} = prefixLog('DEBUG:'); const OVERRIDE_KEY = '$$PREBID_GLOBAL$$:debugging'; export let addBidResponseBound; export let addBidderRequestsBound; -function logMessage(msg) { - utilsLogMessage('DEBUG: ' + msg); -} - -function logWarn(msg) { - utilsLogWarn('DEBUG: ' + msg); -} +export const onEnableOverrides = [ + (overrides) => { + removeHooks(); + addHooks(overrides); + } +]; +export const onDisableOverrides = [ + removeHooks +]; function addHooks(overrides) { addBidResponseBound = addBidResponseHook.bind(overrides); @@ -31,13 +35,12 @@ function removeHooks() { export function enableOverrides(overrides, fromSession = false) { config.setConfig({'debug': true}); - removeHooks(); - addHooks(overrides); + onEnableOverrides.forEach((fn) => fn(overrides)); logMessage(`bidder overrides enabled${fromSession ? ' from session' : ''}`); } export function disableOverrides() { - removeHooks(); + onDisableOverrides.forEach((fn) => fn()); logMessage('bidder overrides disabled'); } @@ -76,6 +79,7 @@ export function applyBidOverrides(overrideObj, bidObj, bidType) { return Object.keys(overrideObj).filter(key => (['adUnitCode', 'bidder'].indexOf(key) === -1)).reduce(function(result, key) { logMessage(`bidder overrides changed '${result.adUnitCode}/${result.bidderCode}' ${bidType}.${key} from '${result[key]}.js' to '${overrideObj[key]}'`); result[key] = overrideObj[key]; + result.isDebug = true; return result; }, bidObj); } @@ -102,7 +106,7 @@ export function addBidResponseHook(next, adUnitCode, bid) { export function addBidderRequestsHook(next, bidderRequests) { const overrides = this; - const includedBidderRequests = bidderRequests.filter(function(bidderRequest) { + const includedBidderRequests = bidderRequests.filter(function (bidderRequest) { if (bidderExcluded(overrides.bidders, bidderRequest.bidderCode)) { logWarn(`bidRequest '${bidderRequest.bidderCode}' excluded from auction by bidder overrides`); return false; @@ -125,19 +129,27 @@ export function addBidderRequestsHook(next, bidderRequests) { next(includedBidderRequests); } -export function getConfig(debugging) { - if (!debugging.enabled) { - disableOverrides(); +export const saveDebuggingConfig = hook('sync', function (debugConfig, {sessionStorage = window.sessionStorage} = {}) { + if (!debugConfig.enabled) { try { - window.sessionStorage.removeItem(OVERRIDE_KEY); + sessionStorage.removeItem(OVERRIDE_KEY); } catch (e) {} } else { try { - window.sessionStorage.setItem(OVERRIDE_KEY, JSON.stringify(debugging)); + sessionStorage.setItem(OVERRIDE_KEY, JSON.stringify(debugConfig)); } catch (e) {} + } +}); + +export function getConfig(debugging, {sessionStorage = window.sessionStorage} = {}) { + saveDebuggingConfig(debugging, {sessionStorage}); + if (!debugging.enabled) { + disableOverrides(); + } else { enableOverrides(debugging); } } + config.getConfig('debugging', ({debugging}) => getConfig(debugging)); export function sessionLoader(storage) { diff --git a/src/hook.js b/src/hook.js index 9050bf2f7dc..2c8e4c7a6e7 100644 --- a/src/hook.js +++ b/src/hook.js @@ -13,14 +13,18 @@ export function setupBeforeHookFnOnce(baseFn, hookFn, priority = 15) { baseFn.before(hookFn, priority); } } +const submoduleInstallMap = {}; -export function module(name, install) { +export function module(name, install, {postInstallAllowed = false} = {}) { hook('async', function (submodules) { submodules.forEach(args => install(...args)); + if (postInstallAllowed) submoduleInstallMap[name] = install; }, name)([]); // will be queued until hook.ready() called in pbjs.processQueue(); } export function submodule(name, ...args) { + const install = submoduleInstallMap[name]; + if (install) return install(...args); getHook(name).before((next, modules) => { modules.push(args); next(modules); diff --git a/src/native.js b/src/native.js index 7596b38534d..a4fd4d763e7 100644 --- a/src/native.js +++ b/src/native.js @@ -1,5 +1,6 @@ -import { deepAccess, getBidRequest, getKeyByValue, insertHtmlIntoIframe, logError, triggerPixel } from './utils.js'; +import { deepAccess, getKeyByValue, insertHtmlIntoIframe, logError, triggerPixel } from './utils.js'; import includes from 'core-js-pure/features/array/includes.js'; +import {auctionManager} from './auctionManager.js'; const CONSTANTS = require('./constants.json'); @@ -35,6 +36,16 @@ export function processNativeAdUnitParams(params) { return params; } +export function decorateAdUnitsWithNativeParams(adUnits) { + adUnits.forEach(adUnit => { + const nativeParams = + adUnit.nativeParams || deepAccess(adUnit, 'mediaTypes.native'); + if (nativeParams) { + adUnit.nativeParams = processNativeAdUnitParams(nativeParams); + } + }); +} + /** * Check if the native type specified in the adUnit is supported by Prebid. */ @@ -68,16 +79,13 @@ export const hasNonNativeBidder = adUnit => * @param {BidRequest[]} bidRequests All bid requests for an auction * @return {Boolean} If object is valid */ -export function nativeBidIsValid(bid, bidRequests) { - const bidRequest = getBidRequest(bid.requestId, bidRequests); - if (!bidRequest) { return false; } - +export function nativeBidIsValid(bid, {index = auctionManager.index} = {}) { // all native bid responses must define a landing page url if (!deepAccess(bid, 'native.clickUrl')) { return false; } - const requestedAssets = bidRequest.nativeParams; + const requestedAssets = index.getAdUnit(bid).nativeParams; if (!requestedAssets) { return true; } @@ -139,21 +147,21 @@ export function fireNativeTrackers(message, adObject) { * @param {Object} bid * @return {Object} targeting */ -export function getNativeTargeting(bid, bidReq) { +export function getNativeTargeting(bid, {index = auctionManager.index} = {}) { let keyValues = {}; - - if (deepAccess(bidReq, 'nativeParams.rendererUrl')) { - bid['native']['rendererUrl'] = getAssetValue(bidReq.nativeParams['rendererUrl']); - } else if (deepAccess(bidReq, 'nativeParams.adTemplate')) { - bid['native']['adTemplate'] = getAssetValue(bidReq.nativeParams['adTemplate']); + const adUnit = index.getAdUnit(bid); + if (deepAccess(adUnit, 'nativeParams.rendererUrl')) { + bid['native']['rendererUrl'] = getAssetValue(adUnit.nativeParams['rendererUrl']); + } else if (deepAccess(adUnit, 'nativeParams.adTemplate')) { + bid['native']['adTemplate'] = getAssetValue(adUnit.nativeParams['adTemplate']); } const globalSendTargetingKeys = deepAccess( - bidReq, + adUnit, `nativeParams.sendTargetingKeys` ) !== false; - const nativeKeys = getNativeKeys(bidReq); + const nativeKeys = getNativeKeys(adUnit); const flatBidNativeKeys = { ...bid.native, ...bid.native.ext }; delete flatBidNativeKeys.ext; @@ -166,9 +174,9 @@ export function getNativeTargeting(bid, bidReq) { return; } - let sendPlaceholder = deepAccess(bidReq, `nativeParams.${asset}.sendId`); + let sendPlaceholder = deepAccess(adUnit, `nativeParams.${asset}.sendId`); if (typeof sendPlaceholder !== 'boolean') { - sendPlaceholder = deepAccess(bidReq, `nativeParams.ext.${asset}.sendId`); + sendPlaceholder = deepAccess(adUnit, `nativeParams.ext.${asset}.sendId`); } if (sendPlaceholder) { @@ -176,9 +184,9 @@ export function getNativeTargeting(bid, bidReq) { value = placeholder; } - let assetSendTargetingKeys = deepAccess(bidReq, `nativeParams.${asset}.sendTargetingKeys`) + let assetSendTargetingKeys = deepAccess(adUnit, `nativeParams.${asset}.sendTargetingKeys`) if (typeof assetSendTargetingKeys !== 'boolean') { - assetSendTargetingKeys = deepAccess(bidReq, `nativeParams.ext.${asset}.sendTargetingKeys`); + assetSendTargetingKeys = deepAccess(adUnit, `nativeParams.ext.${asset}.sendTargetingKeys`); } const sendTargeting = typeof assetSendTargetingKeys === 'boolean' ? assetSendTargetingKeys : globalSendTargetingKeys; @@ -259,11 +267,11 @@ function getAssetValue(value) { return value; } -function getNativeKeys(bidReq) { +function getNativeKeys(adUnit) { const extraNativeKeys = {} - if (deepAccess(bidReq, 'nativeParams.ext')) { - Object.keys(bidReq.nativeParams.ext).forEach(extKey => { + if (deepAccess(adUnit, 'nativeParams.ext')) { + Object.keys(adUnit.nativeParams.ext).forEach(extKey => { extraNativeKeys[extKey] = `hb_native_${extKey}`; }) } diff --git a/src/prebid.js b/src/prebid.js index 2211e8ec2b5..7c8420cac29 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,7 +1,12 @@ /** @module pbjs */ import { getGlobal } from './prebidGlobal.js'; -import { adUnitsFilter, flatten, getHighestCpm, isArrayOfNums, isGptPubadsDefined, uniques } from './utils.js'; +import { + adUnitsFilter, flatten, getHighestCpm, isArrayOfNums, isGptPubadsDefined, uniques, logInfo, + contains, logError, isArray, deepClone, deepAccess, isNumber, logWarn, logMessage, isFn, + transformAdServerTargetingObj, bind, replaceAuctionPrice, replaceClickThrough, insertElement, + inIframe, callBurl, createInvisibleIframe, generateUUID, unsupportedBidderMessage, isEmpty +} from './utils.js'; import { listenMessagesFromCreative } from './secureCreatives.js'; import { userSync } from './userSync.js'; import { config } from './config.js'; @@ -14,16 +19,17 @@ import { adunitCounter } from './adUnits.js'; import { executeRenderer, isRendererRequired } from './Renderer.js'; import { createBid } from './bidfactory.js'; import { storageCallbacks } from './storageManager.js'; +import { emitAdRenderSucceeded, emitAdRenderFail } from './adRendering.js'; +import { gdprDataHandler, uspDataHandler } from './adapterManager.js' const $$PREBID_GLOBAL$$ = getGlobal(); const CONSTANTS = require('./constants.json'); -const utils = require('./utils.js'); const adapterManager = require('./adapterManager.js').default; const events = require('./events.js'); const { triggerUserSyncs } = userSync; /* private variables */ -const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, AD_RENDER_FAILED } = CONSTANTS.EVENTS; +const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, STALE_RENDER } = CONSTANTS.EVENTS; const { PREVENT_WRITING_ON_MAIN_DOCUMENT, NO_AD, EXCEPTION, CANNOT_FIND_AD, MISSING_DOC_OR_ADID } = CONSTANTS.AD_RENDER_FAILED_REASON; const eventValidators = { @@ -41,10 +47,9 @@ $$PREBID_GLOBAL$$.libLoaded = true; // version auto generated from build $$PREBID_GLOBAL$$.version = 'v$prebid.version$'; -utils.logInfo('Prebid.js v$prebid.version$ loaded'); +logInfo('Prebid.js v$prebid.version$ loaded'); -// modules list generated from build -$$PREBID_GLOBAL$$.installedModules = ['v$prebid.modulesList$']; +$$PREBID_GLOBAL$$.installedModules = $$PREBID_GLOBAL$$.installedModules || []; // create adUnit array $$PREBID_GLOBAL$$.adUnits = $$PREBID_GLOBAL$$.adUnits || []; @@ -57,8 +62,8 @@ function checkDefinedPlacement(id) { .reduce(flatten) .filter(uniques); - if (!utils.contains(adUnitCodes, id)) { - utils.logError('The "' + id + '" placement is not defined.'); + if (!contains(adUnitCodes, id)) { + logError('The "' + id + '" placement is not defined.'); return; } @@ -74,7 +79,7 @@ function setRenderSize(doc, width, height) { function validateSizes(sizes, targLength) { let cleanSizes = []; - if (utils.isArray(sizes) && ((targLength) ? sizes.length === targLength : sizes.length > 0)) { + if (isArray(sizes) && ((targLength) ? sizes.length === targLength : sizes.length > 0)) { // check if an array of arrays or array of numbers if (sizes.every(sz => isArrayOfNums(sz, 2))) { cleanSizes = sizes; @@ -86,7 +91,7 @@ function validateSizes(sizes, targLength) { } function validateBannerMediaType(adUnit) { - const validatedAdUnit = utils.deepClone(adUnit); + const validatedAdUnit = deepClone(adUnit); const banner = validatedAdUnit.mediaTypes.banner; const bannerSizes = validateSizes(banner.sizes); if (bannerSizes.length > 0) { @@ -94,14 +99,14 @@ function validateBannerMediaType(adUnit) { // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.banner.sizes validatedAdUnit.sizes = bannerSizes; } else { - utils.logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.'); + logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.'); delete validatedAdUnit.mediaTypes.banner } return validatedAdUnit; } function validateVideoMediaType(adUnit) { - const validatedAdUnit = utils.deepClone(adUnit); + const validatedAdUnit = deepClone(adUnit); const video = validatedAdUnit.mediaTypes.video; if (video.playerSize) { let tarPlayerSizeLen = (typeof video.playerSize[0] === 'number') ? 2 : 1; @@ -109,13 +114,13 @@ function validateVideoMediaType(adUnit) { const videoSizes = validateSizes(video.playerSize, tarPlayerSizeLen); if (videoSizes.length > 0) { if (tarPlayerSizeLen === 2) { - utils.logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.'); + logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.'); } video.playerSize = videoSizes; // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.video.playerSize validatedAdUnit.sizes = videoSizes; } else { - utils.logError('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.'); + logError('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.'); delete validatedAdUnit.mediaTypes.video.playerSize; } } @@ -123,23 +128,37 @@ function validateVideoMediaType(adUnit) { } function validateNativeMediaType(adUnit) { - const validatedAdUnit = utils.deepClone(adUnit); + const validatedAdUnit = deepClone(adUnit); const native = validatedAdUnit.mediaTypes.native; if (native.image && native.image.sizes && !Array.isArray(native.image.sizes)) { - utils.logError('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.'); + logError('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.'); delete validatedAdUnit.mediaTypes.native.image.sizes; } if (native.image && native.image.aspect_ratios && !Array.isArray(native.image.aspect_ratios)) { - utils.logError('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.'); + logError('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.'); delete validatedAdUnit.mediaTypes.native.image.aspect_ratios; } if (native.icon && native.icon.sizes && !Array.isArray(native.icon.sizes)) { - utils.logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.'); + logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.'); delete validatedAdUnit.mediaTypes.native.icon.sizes; } return validatedAdUnit; } +function validateAdUnitPos(adUnit, mediaType) { + let pos = deepAccess(adUnit, `mediaTypes.${mediaType}.pos`); + + if (!isNumber(pos) || isNaN(pos) || !isFinite(pos)) { + let warning = `Value of property 'pos' on ad unit ${adUnit.code} should be of type: Number`; + + logWarn(warning); + events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: warning}); + delete adUnit.mediaTypes[mediaType].pos; + } + + return adUnit +} + export const adUnitSetupChecks = { validateBannerMediaType, validateVideoMediaType, @@ -155,22 +174,24 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) { const bids = adUnit.bids; let validatedBanner, validatedVideo, validatedNative; - if (!bids || !utils.isArray(bids)) { - utils.logError(`Detected adUnit.code '${adUnit.code}' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.`); + if (!bids || !isArray(bids)) { + logError(`Detected adUnit.code '${adUnit.code}' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.`); return; } if (!mediaTypes || Object.keys(mediaTypes).length === 0) { - utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); + logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); return; } if (mediaTypes.banner) { validatedBanner = validateBannerMediaType(adUnit); + if (mediaTypes.banner.hasOwnProperty('pos')) validatedBanner = validateAdUnitPos(validatedBanner, 'banner'); } if (mediaTypes.video) { validatedVideo = validatedBanner ? validateVideoMediaType(validatedBanner) : validateVideoMediaType(adUnit); + if (mediaTypes.video.hasOwnProperty('pos')) validatedVideo = validateAdUnitPos(validatedVideo, 'video'); } if (mediaTypes.native) { @@ -198,14 +219,14 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) { * @return {Array} returnObj return bids array */ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr', arguments); // call to retrieve bids array if (adunitCode) { var res = $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode(adunitCode); - return utils.transformAdServerTargetingObj(res); + return transformAdServerTargetingObj(res); } else { - utils.logMessage('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'); + logMessage('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'); } }; @@ -223,7 +244,7 @@ $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode = function (adunitCod return bid.length ? bid.reduce(getHighestCpm) : {} } else { - utils.logMessage('Need to call getHighestUnusedBidResponseForAdUnitCode with adunitCode'); + logMessage('Need to call getHighestUnusedBidResponseForAdUnitCode with adunitCode'); } }; @@ -244,13 +265,31 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function (adUnitCode) { */ $$PREBID_GLOBAL$$.getAdserverTargeting = function (adUnitCode) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargeting', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargeting', arguments); return targeting.getAllTargeting(adUnitCode); }; +/** + * returns all consent data + * @return {Object} Map of consent types and data + * @alias module:pbjs.getConsentData + */ +function getConsentMetadata() { + return { + gdpr: gdprDataHandler.getConsentMeta(), + usp: uspDataHandler.getConsentMeta(), + coppa: !!(config.getConfig('coppa')) + } +} + +$$PREBID_GLOBAL$$.getConsentMetadata = function () { + logInfo('Invoking $$PREBID_GLOBAL$$.getConsentMetadata'); + return getConsentMetadata(); +}; + function getBids(type) { const responses = auctionManager[type]() - .filter(utils.bind.call(adUnitsFilter, this, auctionManager.getAdUnitCodes())); + .filter(bind.call(adUnitsFilter, this, auctionManager.getAdUnitCodes())); // find the last auction id to get responses for most recent auction only const currentAuctionId = auctionManager.getLastAuctionId(); @@ -275,7 +314,7 @@ function getBids(type) { */ $$PREBID_GLOBAL$$.getNoBids = function () { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.getNoBids', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.getNoBids', arguments); return getBids('getNoBids'); }; @@ -298,7 +337,7 @@ $$PREBID_GLOBAL$$.getNoBidsForAdUnitCode = function (adUnitCode) { */ $$PREBID_GLOBAL$$.getBidResponses = function () { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.getBidResponses', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.getBidResponses', arguments); return getBids('getBidsReceived'); }; @@ -321,9 +360,9 @@ $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode = function (adUnitCode) { * @alias module:pbjs.setTargetingForGPTAsync */ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForGPTAsync', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForGPTAsync', arguments); if (!isGptPubadsDefined()) { - utils.logError('window.googletag is not defined on the page'); + logError('window.googletag is not defined on the page'); return; } @@ -354,9 +393,9 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit, customSlotMatching * @alias module:pbjs.setTargetingForAst */ $$PREBID_GLOBAL$$.setTargetingForAst = function (adUnitCodes) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForAn', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForAn', arguments); if (!targeting.isApntagDefined()) { - utils.logError('window.apntag is not defined on the page'); + logError('window.apntag is not defined on the page'); return; } @@ -366,87 +405,109 @@ $$PREBID_GLOBAL$$.setTargetingForAst = function (adUnitCodes) { events.emit(SET_TARGETING, targeting.getAllTargeting()); }; -function emitAdRenderFail({ reason, message, bid, id }) { - const data = { reason, message }; - if (bid) data.bid = bid; - if (id) data.adId = id; - - utils.logError(message); - events.emit(AD_RENDER_FAILED, data); +/** + * This function will check for presence of given node in given parent. If not present - will inject it. + * @param {Node} node node, whose existance is in question + * @param {Document} doc document element do look in + * @param {string} tagName tag name to look in + */ +function reinjectNodeIfRemoved(node, doc, tagName) { + const injectionNode = doc.querySelector(tagName); + if (!node.parentNode || node.parentNode !== injectionNode) { + insertElement(node, doc, tagName); + } } /** * This function will render the ad (based on params) in the given iframe document passed through. * Note that doc SHOULD NOT be the parent document page as we can't doc.write() asynchronously - * @param {HTMLDocument} doc document + * @param {Document} doc document * @param {string} id bid id to locate the ad * @alias module:pbjs.renderAd */ -$$PREBID_GLOBAL$$.renderAd = function (doc, id, options) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.renderAd', arguments); - utils.logMessage('Calling renderAd with adId :' + id); +$$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { + logInfo('Invoking $$PREBID_GLOBAL$$.renderAd', arguments); + logMessage('Calling renderAd with adId :' + id); if (doc && id) { try { // lookup ad by ad Id const bid = auctionManager.findBidByAdId(id); + if (bid) { - // replace macros according to openRTB with price paid = bid.cpm - bid.ad = utils.replaceAuctionPrice(bid.ad, bid.cpm); - bid.adUrl = utils.replaceAuctionPrice(bid.adUrl, bid.cpm); - - // replacing clickthrough if submitted - if (options && options.clickThrough) { - const { clickThrough } = options; - bid.ad = utils.replaceClickThrough(bid.ad, clickThrough); - bid.adUrl = utils.replaceClickThrough(bid.adUrl, clickThrough); + let shouldRender = true; + if (bid && bid.status === CONSTANTS.BID_STATUS.RENDERED) { + logWarn(`Ad id ${bid.adId} has been rendered before`); + events.emit(STALE_RENDER, bid); + if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { + shouldRender = false; + } } - // save winning bids - auctionManager.addWinningBid(bid); - - // emit 'bid won' event here - events.emit(BID_WON, bid); - - const { height, width, ad, mediaType, adUrl, renderer } = bid; - - const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`); - utils.insertElement(creativeComment, doc, 'body'); - - if (isRendererRequired(renderer)) { - executeRenderer(renderer, bid); - } else if ((doc === document && !utils.inIframe()) || mediaType === 'video') { - const message = `Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`; - emitAdRenderFail({ reason: PREVENT_WRITING_ON_MAIN_DOCUMENT, message, bid, id }); - } else if (ad) { - // will check if browser is firefox and below version 67, if so execute special doc.open() - // for details see: https://github.com/prebid/Prebid.js/pull/3524 - // TODO remove this browser specific code at later date (when Firefox < 67 usage is mostly gone) - if (navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox/') > -1) { - const firefoxVerRegx = /firefox\/([\d\.]+)/; - let firefoxVer = navigator.userAgent.toLowerCase().match(firefoxVerRegx)[1]; // grabs the text in the 1st matching group - if (firefoxVer && parseInt(firefoxVer, 10) < 67) { - doc.open('text/html', 'replace'); + if (shouldRender) { + // replace macros according to openRTB with price paid = bid.cpm + bid.ad = replaceAuctionPrice(bid.ad, bid.cpm); + bid.adUrl = replaceAuctionPrice(bid.adUrl, bid.cpm); + + // replacing clickthrough if submitted + if (options && options.clickThrough) { + const {clickThrough} = options; + bid.ad = replaceClickThrough(bid.ad, clickThrough); + bid.adUrl = replaceClickThrough(bid.adUrl, clickThrough); + } + + // save winning bids + auctionManager.addWinningBid(bid); + + // emit 'bid won' event here + events.emit(BID_WON, bid); + + const {height, width, ad, mediaType, adUrl, renderer} = bid; + + const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`); + insertElement(creativeComment, doc, 'html'); + + if (isRendererRequired(renderer)) { + executeRenderer(renderer, bid); + reinjectNodeIfRemoved(creativeComment, doc, 'html'); + emitAdRenderSucceeded({ doc, bid, id }); + } else if ((doc === document && !inIframe()) || mediaType === 'video') { + const message = `Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`; + emitAdRenderFail({reason: PREVENT_WRITING_ON_MAIN_DOCUMENT, message, bid, id}); + } else if (ad) { + // will check if browser is firefox and below version 67, if so execute special doc.open() + // for details see: https://github.com/prebid/Prebid.js/pull/3524 + // TODO remove this browser specific code at later date (when Firefox < 67 usage is mostly gone) + if (navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox/') > -1) { + const firefoxVerRegx = /firefox\/([\d\.]+)/; + let firefoxVer = navigator.userAgent.toLowerCase().match(firefoxVerRegx)[1]; // grabs the text in the 1st matching group + if (firefoxVer && parseInt(firefoxVer, 10) < 67) { + doc.open('text/html', 'replace'); + } } + doc.write(ad); + doc.close(); + setRenderSize(doc, width, height); + reinjectNodeIfRemoved(creativeComment, doc, 'html'); + callBurl(bid); + emitAdRenderSucceeded({ doc, bid, id }); + } else if (adUrl) { + const iframe = createInvisibleIframe(); + iframe.height = height; + iframe.width = width; + iframe.style.display = 'inline'; + iframe.style.overflow = 'hidden'; + iframe.src = adUrl; + + insertElement(iframe, doc, 'body'); + setRenderSize(doc, width, height); + reinjectNodeIfRemoved(creativeComment, doc, 'html'); + callBurl(bid); + emitAdRenderSucceeded({ doc, bid, id }); + } else { + const message = `Error trying to write ad. No ad for bid response id: ${id}`; + emitAdRenderFail({reason: NO_AD, message, bid, id}); } - doc.write(ad); - doc.close(); - setRenderSize(doc, width, height); - utils.callBurl(bid); - } else if (adUrl) { - const iframe = utils.createInvisibleIframe(); - iframe.height = height; - iframe.width = width; - iframe.style.display = 'inline'; - iframe.style.overflow = 'hidden'; - iframe.src = adUrl; - - utils.insertElement(iframe, doc, 'body'); - setRenderSize(doc, width, height); - utils.callBurl(bid); - } else { - const message = `Error trying to write ad. No ad for bid response id: ${id}`; - emitAdRenderFail({ reason: NO_AD, message, bid, id }); } } else { const message = `Error trying to write ad. Cannot find ad by given id : ${id}`; @@ -460,7 +521,7 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id, options) { const message = `Error trying to write ad Id :${id} to the page. Missing document or adId`; emitAdRenderFail({ reason: MISSING_DOC_OR_ADID, message, id }); } -}; +}); /** * Remove adUnit from the $$PREBID_GLOBAL$$ configuration, if there are no addUnitCode(s) it will remove all @@ -468,7 +529,7 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id, options) { * @alias module:pbjs.removeAdUnit */ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.removeAdUnit', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.removeAdUnit', arguments); if (!adUnitCode) { $$PREBID_GLOBAL$$.adUnits = []; @@ -477,7 +538,7 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { let adUnitCodes; - if (utils.isArray(adUnitCode)) { + if (isArray(adUnitCode)) { adUnitCodes = adUnitCode; } else { adUnitCodes = [adUnitCode]; @@ -505,9 +566,9 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels, auctionId } = {}) { events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); - adUnits = (adUnits && config.convertAdUnitFpd(utils.isArray(adUnits) ? adUnits : [adUnits])) || $$PREBID_GLOBAL$$.adUnits; + adUnits = (adUnits && config.convertAdUnitFpd(isArray(adUnits) ? adUnits : [adUnits])) || $$PREBID_GLOBAL$$.adUnits; - utils.logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); let _s2sConfigs = []; const s2sBidders = []; @@ -547,7 +608,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo const bidders = (s2sBidders) ? allBidders.filter(bidder => !includes(s2sBidders, bidder)) : allBidders; - adUnit.transactionId = utils.generateUUID(); + adUnit.transactionId = generateUUID(); bidders.forEach(bidder => { const adapter = bidderRegistry[bidder]; @@ -559,7 +620,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo const bidderEligible = adUnitMediaTypes.some(type => includes(bidderMediaTypes, type)); if (!bidderEligible) { // drop the bidder from the ad unit if it's not compatible - utils.logWarn(utils.unsupportedBidderMessage(adUnit, bidder)); + logWarn(unsupportedBidderMessage(adUnit, bidder)); adUnit.bids = adUnit.bids.filter(bid => bid.bidder !== bidder); } else { adunitCounter.incrementBidderRequestsCounter(adUnit.code, bidder); @@ -569,13 +630,13 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo }); if (!adUnits || adUnits.length === 0) { - utils.logMessage('No adUnits configured. No bids requested.'); + logMessage('No adUnits configured. No bids requested.'); if (typeof bidsBackHandler === 'function') { // executeCallback, this will only be called in case of first request try { bidsBackHandler(); } catch (e) { - utils.logError('Error executing bidsBackHandler', null, e); + logError('Error executing bidsBackHandler', null, e); } } return; @@ -585,7 +646,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo let adUnitsLen = adUnits.length; if (adUnitsLen > 15) { - utils.logInfo(`Current auction ${auction.getAuctionId()} contains ${adUnitsLen} adUnits.`, adUnits); + logInfo(`Current auction ${auction.getAuctionId()} contains ${adUnitsLen} adUnits.`, adUnits); } adUnitCodes.forEach(code => targeting.setLatestAuctionForAdUnit(code, auction.getAuctionId())); @@ -615,8 +676,8 @@ $$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); * @alias module:pbjs.addAdUnits */ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); - $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, config.convertAdUnitFpd(utils.isArray(adUnitArr) ? adUnitArr : [adUnitArr])); + logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); + $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, config.convertAdUnitFpd(isArray(adUnitArr) ? adUnitArr : [adUnitArr])); // emit event events.emit(ADD_AD_UNITS); }; @@ -638,14 +699,14 @@ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { * Currently `bidWon` is the only event that accepts an `id` parameter. */ $$PREBID_GLOBAL$$.onEvent = function (event, handler, id) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.onEvent', arguments); - if (!utils.isFn(handler)) { - utils.logError('The event handler provided is not a function and was not set on event "' + event + '".'); + logInfo('Invoking $$PREBID_GLOBAL$$.onEvent', arguments); + if (!isFn(handler)) { + logError('The event handler provided is not a function and was not set on event "' + event + '".'); return; } if (id && !eventValidators[event].call(null, id)) { - utils.logError('The id provided is not valid for event "' + event + '" and no handler was set.'); + logError('The id provided is not valid for event "' + event + '" and no handler was set.'); return; } @@ -659,7 +720,7 @@ $$PREBID_GLOBAL$$.onEvent = function (event, handler, id) { * @alias module:pbjs.offEvent */ $$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.offEvent', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.offEvent', arguments); if (id && !eventValidators[event].call(null, id)) { return; } @@ -673,7 +734,7 @@ $$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { * @alias module:pbjs.getEvents */ $$PREBID_GLOBAL$$.getEvents = function () { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.getEvents'); + logInfo('Invoking $$PREBID_GLOBAL$$.getEvents'); return events.getEvents(); }; @@ -684,11 +745,11 @@ $$PREBID_GLOBAL$$.getEvents = function () { * @alias module:pbjs.registerBidAdapter */ $$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.registerBidAdapter', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.registerBidAdapter', arguments); try { adapterManager.registerBidAdapter(bidderAdaptor(), bidderCode); } catch (e) { - utils.logError('Error registering bidder adapter : ' + e.message); + logError('Error registering bidder adapter : ' + e.message); } }; @@ -698,11 +759,11 @@ $$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { * @alias module:pbjs.registerAnalyticsAdapter */ $$PREBID_GLOBAL$$.registerAnalyticsAdapter = function (options) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.registerAnalyticsAdapter', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.registerAnalyticsAdapter', arguments); try { adapterManager.registerAnalyticsAdapter(options); } catch (e) { - utils.logError('Error registering analytics adapter : ' + e.message); + logError('Error registering analytics adapter : ' + e.message); } }; @@ -713,7 +774,7 @@ $$PREBID_GLOBAL$$.registerAnalyticsAdapter = function (options) { * @return {Object} bidResponse [description] */ $$PREBID_GLOBAL$$.createBid = function (statusCode) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.createBid', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.createBid', arguments); return createBid(statusCode); }; @@ -736,11 +797,11 @@ $$PREBID_GLOBAL$$.createBid = function (statusCode) { const enableAnalyticsCallbacks = []; const enableAnalyticsCb = hook('async', function (config) { - if (config && !utils.isEmpty(config)) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.enableAnalytics for: ', config); + if (config && !isEmpty(config)) { + logInfo('Invoking $$PREBID_GLOBAL$$.enableAnalytics for: ', config); adapterManager.enableAnalytics(config); } else { - utils.logError('$$PREBID_GLOBAL$$.enableAnalytics should be called with option {}'); + logError('$$PREBID_GLOBAL$$.enableAnalytics should be called with option {}'); } }, 'enableAnalyticsCb'); @@ -752,11 +813,11 @@ $$PREBID_GLOBAL$$.enableAnalytics = function (config) { * @alias module:pbjs.aliasBidder */ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.aliasBidder', arguments); + logInfo('Invoking $$PREBID_GLOBAL$$.aliasBidder', arguments); if (bidderCode && alias) { adapterManager.aliasBidAdapter(bidderCode, alias, options); } else { - utils.logError('bidderCode and alias must be passed as arguments', '$$PREBID_GLOBAL$$.aliasBidder'); + logError('bidderCode and alias must be passed as arguments', '$$PREBID_GLOBAL$$.aliasBidder'); } }; @@ -842,7 +903,7 @@ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { } else if (markBidRequest.adId) { bids = auctionManager.getBidsReceived().filter(bid => bid.adId === markBidRequest.adId); } else { - utils.logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); + logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); } if (bids.length > 0) { @@ -856,6 +917,9 @@ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { * @alias module:pbjs.getConfig */ $$PREBID_GLOBAL$$.getConfig = config.getConfig; +$$PREBID_GLOBAL$$.readConfig = config.readConfig; +$$PREBID_GLOBAL$$.mergeConfig = config.mergeConfig; +$$PREBID_GLOBAL$$.mergeBidderConfig = config.mergeBidderConfig; /** * Set Prebid config options. @@ -933,10 +997,10 @@ $$PREBID_GLOBAL$$.cmd.push = function (command) { try { command.call(); } catch (e) { - utils.logError('Error processing command :', e.message, e.stack); + logError('Error processing command :', e.message, e.stack); } } else { - utils.logError('Commands written into $$PREBID_GLOBAL$$.cmd.push must be wrapped in a function'); + logError('Commands written into $$PREBID_GLOBAL$$.cmd.push must be wrapped in a function'); } }; @@ -949,7 +1013,7 @@ function processQueue(queue) { cmd.call(); cmd.called = true; } catch (e) { - utils.logError('Error processing command :', 'prebid.js', e); + logError('Error processing command :', 'prebid.js', e); } } }); diff --git a/src/refererDetection.js b/src/refererDetection.js index 56d1fa43f7b..7e9f2a7e6c7 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -42,6 +42,10 @@ export function detectReferer(win) { * @returns {string|null} */ function getCanonicalUrl(doc) { + let pageURL = config.getConfig('pageUrl'); + + if (pageURL) return pageURL; + try { const element = doc.querySelector("link[rel='canonical']"); diff --git a/src/secureCreatives.js b/src/secureCreatives.js index def1a9abdbb..cf0db1e5b39 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -4,21 +4,48 @@ */ import events from './events.js'; -import { fireNativeTrackers, getAssetMessage, getAllAssetsMessage } from './native.js'; +import {fireNativeTrackers, getAllAssetsMessage, getAssetMessage} from './native.js'; import constants from './constants.json'; -import { logWarn, replaceAuctionPrice } from './utils.js'; -import { auctionManager } from './auctionManager.js'; +import {deepAccess, isApnGetTagDefined, isGptPubadsDefined, logError, logWarn, replaceAuctionPrice} from './utils.js'; +import {auctionManager} from './auctionManager.js'; import find from 'core-js-pure/features/array/find.js'; -import { isRendererRequired, executeRenderer } from './Renderer.js'; +import {executeRenderer, isRendererRequired} from './Renderer.js'; import includes from 'core-js-pure/features/array/includes.js'; +import {config} from './config.js'; +import {emitAdRenderFail, emitAdRenderSucceeded} from './adRendering.js'; const BID_WON = constants.EVENTS.BID_WON; +const STALE_RENDER = constants.EVENTS.STALE_RENDER; + +const HANDLER_MAP = { + 'Prebid Request': handleRenderRequest, + 'Prebid Native': handleNativeRequest, + 'Prebid Event': handleEventRequest, +} export function listenMessagesFromCreative() { window.addEventListener('message', receiveMessage, false); } -function receiveMessage(ev) { +export function getReplier(ev) { + if (ev.origin == null && ev.ports.length === 0) { + return function () { + const msg = 'Cannot post message to a frame with null origin. Please update creatives to use MessageChannel, see https://github.com/prebid/Prebid.js/issues/7870' + logError(msg) + throw new Error(msg); + } + } else if (ev.ports.length > 0) { + return function (message) { + ev.ports[0].postMessage(JSON.stringify(message)); + } + } else { + return function (message) { + ev.source.postMessage(JSON.stringify(message), ev.origin); + } + } +} + +export function receiveMessage(ev) { var key = ev.message ? 'message' : 'data'; var data = {}; try { @@ -27,63 +54,128 @@ function receiveMessage(ev) { return; } - if (data && data.adId) { + if (data && data.adId && data.message) { const adObject = find(auctionManager.getBidsReceived(), function (bid) { return bid.adId === data.adId; }); + if (HANDLER_MAP.hasOwnProperty(data.message)) { + HANDLER_MAP[data.message](getReplier(ev), data, adObject); + } + } +} - if (adObject && data.message === 'Prebid Request') { - _sendAdToCreative(adObject, ev); +function handleRenderRequest(reply, data, adObject) { + if (adObject == null) { + emitAdRenderFail({ + reason: constants.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + message: `Cannot find ad '${data.adId}' for cross-origin render request`, + id: data.adId + }); + return; + } + if (adObject.status === constants.BID_STATUS.RENDERED) { + logWarn(`Ad id ${adObject.adId} has been rendered before`); + events.emit(STALE_RENDER, adObject); + if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { + return; + } + } - // save winning bids - auctionManager.addWinningBid(adObject); + try { + _sendAdToCreative(adObject, reply); + } catch (e) { + emitAdRenderFail({ + reason: constants.AD_RENDER_FAILED_REASON.EXCEPTION, + message: e.message, + id: data.adId, + bid: adObject + }); + return; + } - events.emit(BID_WON, adObject); - } + // save winning bids + auctionManager.addWinningBid(adObject); - // handle this script from native template in an ad server - // window.parent.postMessage(JSON.stringify({ - // message: 'Prebid Native', - // adId: '%%PATTERN:hb_adid%%' - // }), '*'); - if (adObject && data.message === 'Prebid Native') { - if (data.action === 'assetRequest') { - const message = getAssetMessage(data, adObject); - ev.source.postMessage(JSON.stringify(message), ev.origin); - return; - } else if (data.action === 'allAssetRequest') { - const message = getAllAssetsMessage(data, adObject); - ev.source.postMessage(JSON.stringify(message), ev.origin); - } else if (data.action === 'resizeNativeHeight') { - adObject.height = data.height; - adObject.width = data.width; - resizeRemoteCreative(adObject); - } + events.emit(BID_WON, adObject); +} +function handleNativeRequest(reply, data, adObject) { + // handle this script from native template in an ad server + // window.parent.postMessage(JSON.stringify({ + // message: 'Prebid Native', + // adId: '%%PATTERN:hb_adid%%' + // }), '*'); + if (adObject == null) { + logError(`Cannot find ad '${data.adId}' for x-origin event request`); + return; + } + switch (data.action) { + case 'assetRequest': + reply(getAssetMessage(data, adObject)); + break; + case 'allAssetRequest': + reply(getAllAssetsMessage(data, adObject)); + break; + case 'resizeNativeHeight': + adObject.height = data.height; + adObject.width = data.width; + resizeRemoteCreative(adObject); + break; + default: const trackerType = fireNativeTrackers(data, adObject); - if (trackerType === 'click') { return; } - + if (trackerType === 'click') { + return; + } auctionManager.addWinningBid(adObject); events.emit(BID_WON, adObject); - } } } -export function _sendAdToCreative(adObject, ev) { +function handleEventRequest(reply, data, adObject) { + if (adObject == null) { + logError(`Cannot find ad '${data.adId}' for x-origin event request`); + return; + } + if (adObject.status !== constants.BID_STATUS.RENDERED) { + logWarn(`Received x-origin event request without corresponding render request for ad '${data.adId}'`); + return; + } + switch (data.event) { + case constants.EVENTS.AD_RENDER_FAILED: + emitAdRenderFail({ + bid: adObject, + id: data.adId, + reason: data.info.reason, + message: data.info.message + }); + break; + case constants.EVENTS.AD_RENDER_SUCCEEDED: + emitAdRenderSucceeded({ + doc: null, + bid: adObject, + id: data.adId + }); + break; + default: + logError(`Received x-origin event request for unsupported event: '${data.event}' (adId: '${data.adId}')`) + } +} + +export function _sendAdToCreative(adObject, reply) { const { adId, ad, adUrl, width, height, renderer, cpm } = adObject; // rendering for outstream safeframe if (isRendererRequired(renderer)) { executeRenderer(renderer, adObject); } else if (adId) { resizeRemoteCreative(adObject); - ev.source.postMessage(JSON.stringify({ + reply({ message: 'Prebid Response', ad: replaceAuctionPrice(ad, cpm), adUrl: replaceAuctionPrice(adUrl, cpm), adId, width, height - }), ev.origin); + }); } } @@ -108,9 +200,9 @@ function resizeRemoteCreative({ adId, adUnitCode, width, height }) { } function getElementIdBasedOnAdServer(adId, adUnitCode) { - if (window.googletag) { + if (isGptPubadsDefined()) { return getDfpElementId(adId) - } else if (window.apntag) { + } else if (isApnGetTagDefined()) { return getAstElementId(adUnitCode) } else { return adUnitCode; @@ -118,11 +210,12 @@ function resizeRemoteCreative({ adId, adUnitCode, width, height }) { } function getDfpElementId(adId) { - return find(window.googletag.pubads().getSlots(), slot => { + const slot = find(window.googletag.pubads().getSlots(), slot => { return find(slot.getTargetingKeys(), key => { return includes(slot.getTargeting(key), adId); }); - }).getSlotElementId(); + }); + return slot ? slot.getSlotElementId() : null; } function getAstElementId(adUnitCode) { diff --git a/src/sizeMapping.js b/src/sizeMapping.js index 313da3f422a..cd5f1190069 100644 --- a/src/sizeMapping.js +++ b/src/sizeMapping.js @@ -121,22 +121,17 @@ function evaluateSizeConfig(configs) { return configs.reduce((results, config) => { if ( typeof config === 'object' && - typeof config.mediaQuery === 'string' + typeof config.mediaQuery === 'string' && + config.mediaQuery.length > 0 ) { let ruleMatch = false; - // TODO: (Prebid - 4.0) Remove empty mediaQuery string check. Disallow empty mediaQuery in sizeConfig. - // Refer: https://github.com/prebid/Prebid.js/pull/4691, https://github.com/prebid/Prebid.js/issues/4810 for more details. - if (config.mediaQuery === '') { - ruleMatch = true; - } else { - try { - ruleMatch = getWindowTop().matchMedia(config.mediaQuery).matches; - } catch (e) { - logWarn('Unfriendly iFrame blocks sizeConfig from being correctly evaluated'); - - ruleMatch = matchMedia(config.mediaQuery).matches; - } + try { + ruleMatch = getWindowTop().matchMedia(config.mediaQuery).matches; + } catch (e) { + logWarn('Unfriendly iFrame blocks sizeConfig from being correctly evaluated'); + + ruleMatch = matchMedia(config.mediaQuery).matches; } if (ruleMatch) { diff --git a/src/storageManager.js b/src/storageManager.js index 66a0cf68cbf..888cdf24325 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,5 +1,5 @@ import {hook} from './hook.js'; -import * as utils from './utils.js'; +import { hasDeviceAccess, checkCookieSupport, logError } from './utils.js'; import includes from 'core-js-pure/features/array/includes.js'; const moduleTypeWhiteList = ['core', 'prebid-module']; @@ -40,7 +40,7 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { } else { let result = { hasEnforcementHook: false, - valid: utils.hasDeviceAccess() + valid: hasDeviceAccess() } value = cb(result); } @@ -135,7 +135,7 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { const cookiesAreEnabled = function (done) { let cb = function (result) { if (result && result.valid) { - if (utils.checkCookieSupport()) { + if (checkCookieSupport()) { return true; } window.document.cookie = 'prebid.cookieTest'; @@ -222,7 +222,7 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { try { return !!window.localStorage; } catch (e) { - utils.logError('Local storage api disabled'); + logError('Local storage api disabled'); } } return false; @@ -247,7 +247,7 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { let cb = function (result) { if (result && result.valid) { const all = []; - if (utils.hasDeviceAccess()) { + if (hasDeviceAccess()) { const cookies = document.cookie.split(';'); while (cookies.length) { const cookie = cookies.pop(); diff --git a/src/targeting.js b/src/targeting.js index 365453e1e8f..a21a2f6eede 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -1,14 +1,17 @@ -import { uniques, isGptPubadsDefined, getHighestCpm, getOldestHighestCpmBid, groupBy, isAdUnitCodeMatchingSlot, timestamp, deepAccess, deepClone, logError, logWarn, logInfo } from './utils.js'; +import { + uniques, isGptPubadsDefined, getHighestCpm, getOldestHighestCpmBid, groupBy, isAdUnitCodeMatchingSlot, timestamp, + deepAccess, deepClone, logError, logWarn, logInfo, isFn, isArray, logMessage, isStr, +} from './utils.js'; import { config } from './config.js'; import { NATIVE_TARGETING_KEYS } from './native.js'; import { auctionManager } from './auctionManager.js'; import { sizeSupported } from './sizeMapping.js'; import { ADPOD } from './mediaTypes.js'; import { hook } from './hook.js'; +import { bidderSettings } from './bidderSettings.js'; import includes from 'core-js-pure/features/array/includes.js'; import find from 'core-js-pure/features/array/find.js'; -const utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); var pbTargetingKeys = []; @@ -16,6 +19,10 @@ var pbTargetingKeys = []; const MAX_DFP_KEYLENGTH = 20; const TTL_BUFFER = 1000; +const CFG_ALLOW_TARGETING_KEYS = `targetingControls.allowTargetingKeys`; +const CFG_ADD_TARGETING_KEYS = `targetingControls.addTargetingKeys`; +const TARGETING_KEY_CONFIGURATION_ERROR_MSG = `Only one of "${CFG_ALLOW_TARGETING_KEYS}" or "${CFG_ADD_TARGETING_KEYS}" can be set`; + export const TARGETING_KEYS = Object.keys(CONSTANTS.TARGETING_KEYS).map( key => CONSTANTS.TARGETING_KEYS[key] ); @@ -121,17 +128,19 @@ export function newTargeting(auctionManager) { if (isGptPubadsDefined()) { const adUnitCodes = getAdUnitCodes(adUnitCode); const adUnits = auctionManager.getAdUnits().filter(adUnit => includes(adUnitCodes, adUnit.code)); + let unsetKeys = pbTargetingKeys.reduce((reducer, key) => { + reducer[key] = null; + return reducer; + }, {}); window.googletag.pubads().getSlots().forEach(slot => { - let customSlotMatchingFunc = utils.isFn(customSlotMatching) && customSlotMatching(slot); - pbTargetingKeys.forEach(function(key) { - // reset only registered adunits - adUnits.forEach(function(unit) { - if (unit.code === slot.getAdUnitPath() || - unit.code === slot.getSlotElementId() || - (utils.isFn(customSlotMatchingFunc) && customSlotMatchingFunc(unit.code))) { - slot.setTargeting(key, null); - } - }); + let customSlotMatchingFunc = isFn(customSlotMatching) && customSlotMatching(slot); + // reset only registered adunits + adUnits.forEach(unit => { + if (unit.code === slot.getAdUnitPath() || + unit.code === slot.getSlotElementId() || + (isFn(customSlotMatchingFunc) && customSlotMatchingFunc(unit.code))) { + slot.updateTargetingFromMap(unsetKeys); + } }); }); } @@ -160,7 +169,7 @@ export function newTargeting(auctionManager) { */ function bidShouldBeAddedToTargeting(bid, adUnitCodes) { return bid.adserverTargeting && adUnitCodes && - ((utils.isArray(adUnitCodes) && includes(adUnitCodes, bid.adUnitCode)) || + ((isArray(adUnitCodes) && includes(adUnitCodes, bid.adUnitCode)) || (typeof adUnitCodes === 'string' && bid.adUnitCode === adUnitCodes)); }; @@ -257,7 +266,17 @@ export function newTargeting(auctionManager) { }); const defaultKeys = Object.keys(Object.assign({}, CONSTANTS.DEFAULT_TARGETING_KEYS, CONSTANTS.NATIVE_KEYS)); - const allowedKeys = config.getConfig('targetingControls.allowTargetingKeys') || defaultKeys; + let allowedKeys = config.getConfig(CFG_ALLOW_TARGETING_KEYS); + const addedKeys = config.getConfig(CFG_ADD_TARGETING_KEYS); + + if (addedKeys != null && allowedKeys != null) { + throw new Error(TARGETING_KEY_CONFIGURATION_ERROR_MSG); + } else if (addedKeys != null) { + allowedKeys = defaultKeys.concat(addedKeys); + } else { + allowedKeys = allowedKeys || defaultKeys; + } + if (Array.isArray(allowedKeys) && allowedKeys.length > 0) { targeting = getAllowedTargetingKeyValues(targeting, allowedKeys); } @@ -280,6 +299,13 @@ export function newTargeting(auctionManager) { return targeting; }; + // warn about conflicting configuration + config.getConfig('targetingControls', function (config) { + if (deepAccess(config, CFG_ALLOW_TARGETING_KEYS) != null && deepAccess(config, CFG_ADD_TARGETING_KEYS) != null) { + logError(TARGETING_KEY_CONFIGURATION_ERROR_MSG); + } + }); + // create an encoded string variant based on the keypairs of the provided object // - note this will encode the characters between the keys (ie = and &) function convertKeysToQueryForm(keyMap) { @@ -337,7 +363,7 @@ export function newTargeting(auctionManager) { * "div-gpt-ad-1460505748561-0": [{"hb_bidder": ["appnexusAst"]}] * }, * { - * "div-gpt-ad-1460505748561-0": [{"hb_bidder_appnexusAs": ["appnexusAst"]}] + * "div-gpt-ad-1460505748561-0": [{"hb_bidder_appnexusAs": ["appnexusAst", "other"]}] * } * ] * ``` @@ -346,7 +372,7 @@ export function newTargeting(auctionManager) { * { * "div-gpt-ad-1460505748561-0": { * "hb_bidder": "appnexusAst", - * "hb_bidder_appnexusAs": "appnexusAst" + * "hb_bidder_appnexusAs": "appnexusAst,other" * } * } * ``` @@ -360,7 +386,7 @@ export function newTargeting(auctionManager) { [Object.keys(targeting)[0]]: targeting[Object.keys(targeting)[0]] .map(target => { return { - [Object.keys(target)[0]]: target[Object.keys(target)[0]].join(', ') + [Object.keys(target)[0]]: target[Object.keys(target)[0]].join(',') }; }).reduce((p, c) => Object.assign(c, p), {}) }; @@ -379,21 +405,18 @@ export function newTargeting(auctionManager) { targeting.setTargetingForGPT = function(targetingConfig, customSlotMatching) { window.googletag.pubads().getSlots().forEach(slot => { Object.keys(targetingConfig).filter(customSlotMatching ? customSlotMatching(slot) : isAdUnitCodeMatchingSlot(slot)) - .forEach(targetId => + .forEach(targetId => { Object.keys(targetingConfig[targetId]).forEach(key => { - let valueArr = targetingConfig[targetId][key]; - if (typeof valueArr === 'string') { - valueArr = valueArr.split(','); + let value = targetingConfig[targetId][key]; + if (typeof value === 'string' && value.indexOf(',') !== -1) { + // due to the check the array will be formed only if string has ',' else plain string will be assigned as value + value = value.split(','); } - valueArr = (valueArr.length > 1) ? [valueArr] : valueArr; - valueArr.map((value) => { - utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${key} value: ${value}`); - return value; - }).forEach(value => { - slot.setTargeting(key, value); - }); - }) - ) + targetingConfig[targetId][key] = value; + }); + logMessage(`Attempting to set targeting-map for slot: ${slot.getSlotElementId()} with targeting-map:`, targetingConfig[targetId]); + slot.updateTargetingFromMap(targetingConfig[targetId]) + }) }) }; @@ -405,7 +428,7 @@ export function newTargeting(auctionManager) { function getAdUnitCodes(adUnitCode) { if (typeof adUnitCode === 'string') { return [adUnitCode]; - } else if (utils.isArray(adUnitCode)) { + } else if (isArray(adUnitCode)) { return adUnitCode; } return auctionManager.getAdUnitCodes() || []; @@ -415,7 +438,15 @@ export function newTargeting(auctionManager) { let bidsReceived = auctionManager.getBidsReceived(); if (!config.getConfig('useBidCache')) { + // don't use bid cache (i.e. filter out bids not in the latest auction) bidsReceived = bidsReceived.filter(bid => latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId) + } else { + // if custom bid cache filter function exists, run for each bid from + // previous auctions. If it returns true, include bid in bid pool + const filterFunction = config.getConfig('bidCacheFilterFunction'); + if (typeof filterFunction === 'function') { + bidsReceived = bidsReceived.filter(bid => latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId || !!filterFunction(bid)) + } } bidsReceived = bidsReceived @@ -437,7 +468,7 @@ export function newTargeting(auctionManager) { const adUnitCodes = getAdUnitCodes(adUnitCode); return bidsReceived .filter(bid => includes(adUnitCodes, bid.adUnitCode)) - .filter(bid => bid.cpm > 0) + .filter(bid => (bidderSettings.get(bid.bidderCode, 'allowZeroCpmBids') === true) ? bid.cpm >= 0 : bid.cpm > 0) .map(bid => bid.adUnitCode) .filter(uniques) .map(adUnitCode => bidsReceived @@ -455,14 +486,14 @@ export function newTargeting(auctionManager) { try { targeting.resetPresetTargetingAST(adUnitCodes); } catch (e) { - utils.logError('unable to reset targeting for AST' + e) + logError('unable to reset targeting for AST' + e) } Object.keys(astTargeting).forEach(targetId => Object.keys(astTargeting[targetId]).forEach(key => { - utils.logMessage(`Attempting to set targeting for targetId: ${targetId} key: ${key} value: ${astTargeting[targetId][key]}`); + logMessage(`Attempting to set targeting for targetId: ${targetId} key: ${key} value: ${astTargeting[targetId][key]}`); // setKeywords supports string and array as value - if (utils.isStr(astTargeting[targetId][key]) || utils.isArray(astTargeting[targetId][key])) { + if (isStr(astTargeting[targetId][key]) || isArray(astTargeting[targetId][key])) { let keywordsObj = {}; let regex = /pt[0-9]/; if (key.search(regex) < 0) { @@ -527,7 +558,7 @@ export function newTargeting(auctionManager) { function mergeAdServerTargeting(acc, bid, index, arr) { function concatTargetingValue(key) { return function(currentBidElement) { - if (!utils.isArray(currentBidElement.adserverTargeting[key])) { + if (!isArray(currentBidElement.adserverTargeting[key])) { currentBidElement.adserverTargeting[key] = [currentBidElement.adserverTargeting[key]]; } currentBidElement.adserverTargeting[key] = currentBidElement.adserverTargeting[key].concat(bid.adserverTargeting[key]).filter(uniques); @@ -597,13 +628,19 @@ export function newTargeting(auctionManager) { const standardKeys = TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS); const adUnitBidLimit = config.getConfig('sendBidsControl.bidLimit'); const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm, adUnitBidLimit); + const allowSendAllBidsTargetingKeys = config.getConfig('targetingControls.allowSendAllBidsTargetingKeys'); + + const allowedSendAllBidTargeting = allowSendAllBidsTargetingKeys + ? allowSendAllBidsTargetingKeys.map((key) => CONSTANTS.TARGETING_KEYS[key]) + : standardKeys; // populate targeting keys for the remaining bids return bids.map(bid => { if (bidShouldBeAddedToTargeting(bid, adUnitCodes)) { return { [bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter( - key => typeof bid.adserverTargeting[key] !== 'undefined') + key => typeof bid.adserverTargeting[key] !== 'undefined' && + allowedSendAllBidTargeting.indexOf(key) !== -1) ) }; } @@ -628,7 +665,9 @@ export function newTargeting(auctionManager) { return Object.keys(aut) .map(function(key) { - return {[key]: utils.isArray(aut[key]) ? aut[key] : aut[key].split(',')}; + if (isStr(aut[key])) aut[key] = aut[key].split(',').map(s => s.trim()); + if (!isArray(aut[key])) aut[key] = [ aut[key] ]; + return { [key]: aut[key] }; }); } @@ -640,7 +679,7 @@ export function newTargeting(auctionManager) { } targeting.isApntagDefined = function() { - if (window.apntag && utils.isFn(window.apntag.setKeywords)) { + if (window.apntag && isFn(window.apntag.setKeywords)) { return true; } }; diff --git a/src/userSync.js b/src/userSync.js index f653880fa29..60e605f29fb 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -1,4 +1,7 @@ -import * as utils from './utils.js'; +import { + deepClone, isPlainObject, logError, shuffle, logMessage, triggerPixel, insertUserSyncIframe, isArray, + logWarn, isStr, isSafariBrowser +} from './utils.js'; import { config } from './config.js'; import includes from 'core-js-pure/features/array/includes.js'; import { getCoreStorageManager } from './storageManager.js'; @@ -18,7 +21,7 @@ export const USERSYNC_DEFAULT_CONFIG = { // Set userSync default values config.setDefaults({ - 'userSync': utils.deepClone(USERSYNC_DEFAULT_CONFIG) + 'userSync': deepClone(USERSYNC_DEFAULT_CONFIG) }); const storage = getCoreStorageManager('usersync'); @@ -54,7 +57,7 @@ export function newUserSync(userSyncDependencies) { // if userSync.filterSettings does not contain image/all configs, merge in default image config to ensure image pixels are fired if (conf.userSync) { let fs = conf.userSync.filterSettings; - if (utils.isPlainObject(fs)) { + if (isPlainObject(fs)) { if (!fs.image && !fs.all) { conf.userSync.filterSettings.image = { bidders: '*', @@ -91,12 +94,12 @@ export function newUserSync(userSyncDependencies) { } try { - // Image pixels - fireImagePixels(); // Iframe syncs loadIframes(); + // Image pixels + fireImagePixels(); } catch (e) { - return utils.logError('Error firing user syncs', e); + return logError('Error firing user syncs', e); } // Reset the user sync queue queue = getDefaultQueue(); @@ -106,7 +109,7 @@ export function newUserSync(userSyncDependencies) { // Randomize the order of the pixels before firing // This is to avoid giving any bidder who has registered multiple syncs // any preferential treatment and balancing them out - utils.shuffle(queue).forEach((sync) => { + shuffle(queue).forEach((sync) => { fn(sync); hasFiredBidder.add(sync[0]); }); @@ -123,9 +126,9 @@ export function newUserSync(userSyncDependencies) { } forEachFire(queue.image, (sync) => { let [bidderName, trackingPixelUrl] = sync; - utils.logMessage(`Invoking image pixel user sync for bidder: ${bidderName}`); + logMessage(`Invoking image pixel user sync for bidder: ${bidderName}`); // Create image object and add the src url - utils.triggerPixel(trackingPixelUrl); + triggerPixel(trackingPixelUrl); }); } @@ -138,11 +141,21 @@ export function newUserSync(userSyncDependencies) { if (!(permittedPixels.iframe)) { return; } + forEachFire(queue.iframe, (sync) => { let [bidderName, iframeUrl] = sync; - utils.logMessage(`Invoking iframe user sync for bidder: ${bidderName}`); + logMessage(`Invoking iframe user sync for bidder: ${bidderName}`); // Insert iframe into DOM - utils.insertUserSyncIframe(iframeUrl); + insertUserSyncIframe(iframeUrl); + // for a bidder, if iframe sync is present then remove image pixel + removeImagePixelsForBidder(queue, bidderName); + }); + } + + function removeImagePixelsForBidder(queue, iframeSyncBidderName) { + queue.image = queue.image.filter(imageSync => { + let imageSyncBidderName = imageSync[0]; + return imageSyncBidderName !== iframeSyncBidderName }); } @@ -177,21 +190,21 @@ export function newUserSync(userSyncDependencies) { */ publicApi.registerSync = (type, bidder, url) => { if (hasFiredBidder.has(bidder)) { - return utils.logMessage(`already fired syncs for "${bidder}", ignoring registerSync call`); + return logMessage(`already fired syncs for "${bidder}", ignoring registerSync call`); } - if (!usConfig.syncEnabled || !utils.isArray(queue[type])) { - return utils.logWarn(`User sync type "${type}" not supported`); + if (!usConfig.syncEnabled || !isArray(queue[type])) { + return logWarn(`User sync type "${type}" not supported`); } if (!bidder) { - return utils.logWarn(`Bidder is required for registering sync`); + return logWarn(`Bidder is required for registering sync`); } if (usConfig.syncsPerBidder !== 0 && Number(numAdapterBids[bidder]) >= usConfig.syncsPerBidder) { - return utils.logWarn(`Number of user syncs exceeded for "${bidder}"`); + return logWarn(`Number of user syncs exceeded for "${bidder}"`); } const canBidderRegisterSync = publicApi.canBidderRegisterSync(type, bidder); if (!canBidderRegisterSync) { - return utils.logWarn(`Bidder "${bidder}" not permitted to register their "${type}" userSync pixels.`); + return logWarn(`Bidder "${bidder}" not permitted to register their "${type}" userSync pixels.`); } // the bidder's pixel has passed all checks and is allowed to register @@ -238,7 +251,7 @@ export function newUserSync(userSyncDependencies) { */ function isFilterConfigValid(filterConfig, type) { if (filterConfig.all && filterConfig[type]) { - utils.logWarn(`Detected presence of the "filterSettings.all" and "filterSettings.${type}" in userSync config. You cannot mix "all" with "iframe/image" configs; they are mutually exclusive.`); + logWarn(`Detected presence of the "filterSettings.all" and "filterSettings.${type}" in userSync config. You cannot mix "all" with "iframe/image" configs; they are mutually exclusive.`); return false; } @@ -255,12 +268,12 @@ export function newUserSync(userSyncDependencies) { let biddersField = activeConfig.bidders; if (filterField && filterField !== 'include' && filterField !== 'exclude') { - utils.logWarn(`UserSync "filterSettings.${activeConfigName}.filter" setting '${filterField}' is not a valid option; use either 'include' or 'exclude'.`); + logWarn(`UserSync "filterSettings.${activeConfigName}.filter" setting '${filterField}' is not a valid option; use either 'include' or 'exclude'.`); return false; } - if (biddersField !== '*' && !(Array.isArray(biddersField) && biddersField.length > 0 && biddersField.every(bidderInList => utils.isStr(bidderInList) && bidderInList !== '*'))) { - utils.logWarn(`Detected an invalid setup in userSync "filterSettings.${activeConfigName}.bidders"; use either '*' (to represent all bidders) or an array of bidders.`); + if (biddersField !== '*' && !(Array.isArray(biddersField) && biddersField.length > 0 && biddersField.every(bidderInList => isStr(bidderInList) && bidderInList !== '*'))) { + logWarn(`Detected an invalid setup in userSync "filterSettings.${activeConfigName}.bidders"; use either '*' (to represent all bidders) or an array of bidders.`); return false; } @@ -302,7 +315,7 @@ export function newUserSync(userSyncDependencies) { return publicApi; } -const browserSupportsCookies = !utils.isSafariBrowser() && storage.cookiesAreEnabled(); +const browserSupportsCookies = !isSafariBrowser() && storage.cookiesAreEnabled(); export const userSync = newUserSync({ config: config.getConfig('userSync'), diff --git a/src/utils.js b/src/utils.js index 35ce3e7d0dd..c30564a4ded 100644 --- a/src/utils.js +++ b/src/utils.js @@ -21,7 +21,17 @@ let consoleLogExists = Boolean(consoleExists && window.console.log); let consoleInfoExists = Boolean(consoleExists && window.console.info); let consoleWarnExists = Boolean(consoleExists && window.console.warn); let consoleErrorExists = Boolean(consoleExists && window.console.error); -var events = require('./events.js'); + +const emitEvent = (function () { + // lazy load events to avoid circular import + let ev; + return function() { + if (ev == null) { + ev = require('./events.js'); + } + return ev.emit.apply(ev, arguments); + } +})(); // this allows stubbing of utility functions that are used internally by other utility functions export const internal = { @@ -264,14 +274,28 @@ export function logWarn() { if (debugTurnedOn() && consoleWarnExists) { console.warn.apply(console, decorateLog(arguments, 'WARNING:')); } - events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: arguments}); + emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: arguments}); } export function logError() { if (debugTurnedOn() && consoleErrorExists) { console.error.apply(console, decorateLog(arguments, 'ERROR:')); } - events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'ERROR', arguments: arguments}); + emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'ERROR', arguments: arguments}); +} + +export function prefixLog(prefix) { + function decorate(fn) { + return function (...args) { + fn(prefix, ...args); + } + } + return { + logError: decorate(logError), + logWarn: decorate(logWarn), + logMessage: decorate(logMessage), + logInfo: decorate(logInfo), + } } function decorateLog(args, prefix) { @@ -481,16 +505,43 @@ export function insertElement(elm, doc, target, asLastChildChild) { } catch (e) {} } +/** + * Returns a promise that completes when the given element triggers a 'load' or 'error' DOM event, or when + * `timeout` milliseconds have elapsed. + * + * @param {HTMLElement} element + * @param {Number} [timeout] + * @returns {Promise} + */ +export function waitForElementToLoad(element, timeout) { + let timer = null; + return new Promise((resolve) => { + const onLoad = function() { + element.removeEventListener('load', onLoad); + element.removeEventListener('error', onLoad); + if (timer != null) { + window.clearTimeout(timer); + } + resolve(); + }; + element.addEventListener('load', onLoad); + element.addEventListener('error', onLoad); + if (timeout != null) { + timer = window.setTimeout(onLoad, timeout); + } + }); +} + /** * Inserts an image pixel with the specified `url` for cookie sync * @param {string} url URL string of the image pixel to load * @param {function} [done] an optional exit callback, used when this usersync pixel is added during an async process + * @param {Number} [timeout] an optional timeout in milliseconds for the image to load before calling `done` */ -export function triggerPixel(url, done) { +export function triggerPixel(url, done, timeout) { const img = new Image(); if (done && internal.isFn(done)) { - img.addEventListener('load', done); - img.addEventListener('error', done); + waitForElementToLoad(img, timeout).then(done); } img.src = url; } @@ -538,18 +589,18 @@ export function insertHtmlIntoIframe(htmlCode) { * @param {string} url URL to be requested * @param {string} encodeUri boolean if URL should be encoded before inserted. Defaults to true * @param {function} [done] an optional exit callback, used when this usersync pixel is added during an async process + * @param {Number} [timeout] an optional timeout in milliseconds for the iframe to load before calling `done` */ -export function insertUserSyncIframe(url, done) { +export function insertUserSyncIframe(url, done, timeout) { let iframeHtml = internal.createTrackPixelIframeHtml(url, false, 'allow-scripts allow-same-origin'); let div = document.createElement('div'); div.innerHTML = iframeHtml; let iframe = div.firstChild; if (done && internal.isFn(done)) { - iframe.addEventListener('load', done); - iframe.addEventListener('error', done); + waitForElementToLoad(iframe, timeout).then(done); } internal.insertElement(iframe, document, 'html', true); -}; +} /** * Creates a snippet of HTML that retrieves the specified `url` @@ -665,6 +716,12 @@ export function isGptPubadsDefined() { } } +export function isApnGetTagDefined() { + if (window.apntag && isFn(window.apntag.getTag)) { + return true; + } +} + // This function will get highest cpm value bid, in case of tie it will return the bid with lowest timeToRespond export const getHighestCpm = getHighestCpmCallback('timeToRespond', (previous, current) => previous > current); @@ -851,12 +908,6 @@ export function isValidMediaTypes(mediaTypes) { return true; } -export function getBidderRequest(bidRequests, bidder, adUnitCode) { - return find(bidRequests, request => { - return request.bids - .filter(bid => bid.bidder === bidder && bid.adUnitCode === adUnitCode).length > 0; - }) || { start: null, auctionId: null }; -} /** * Returns user configured bidder params from adunit * @param {Object} adUnits @@ -1204,15 +1255,21 @@ export function buildUrl(obj) { * This function deeply compares two objects checking for their equivalence. * @param {Object} obj1 * @param {Object} obj2 + * @param checkTypes {boolean} if set, two objects with identical properties but different constructors will *not* + * be considered equivalent. * @returns {boolean} */ -export function deepEqual(obj1, obj2) { +export function deepEqual(obj1, obj2, {checkTypes = false} = {}) { if (obj1 === obj2) return true; - else if ((typeof obj1 === 'object' && obj1 !== null) && (typeof obj2 === 'object' && obj2 !== null)) { + else if ( + (typeof obj1 === 'object' && obj1 !== null) && + (typeof obj2 === 'object' && obj2 !== null) && + (!checkTypes || (obj1.constructor === obj2.constructor)) + ) { if (Object.keys(obj1).length !== Object.keys(obj2).length) return false; for (let prop in obj1) { if (obj2.hasOwnProperty(prop)) { - if (!deepEqual(obj1[prop], obj2[prop])) { + if (!deepEqual(obj1[prop], obj2[prop], {checkTypes})) { return false; } } else { @@ -1236,9 +1293,20 @@ export function mergeDeep(target, ...sources) { mergeDeep(target[key], source[key]); } else if (isArray(source[key])) { if (!target[key]) { - Object.assign(target, { [key]: source[key] }); + Object.assign(target, { [key]: [...source[key]] }); } else if (isArray(target[key])) { - target[key] = target[key].concat(source[key]); + source[key].forEach(obj => { + let addItFlag = 1; + for (let i = 0; i < target[key].length; i++) { + if (deepEqual(target[key][i], obj)) { + addItFlag = 0; + break; + } + } + if (addItFlag) { + target[key].push(obj); + } + }); } } else { Object.assign(target, { [key]: source[key] }); diff --git a/src/video.js b/src/video.js index 20df7a92442..a27e4ba9734 100644 --- a/src/video.js +++ b/src/video.js @@ -1,8 +1,9 @@ import adapterManager from './adapterManager.js'; -import { getBidRequest, deepAccess, logError } from './utils.js'; +import { deepAccess, logError } from './utils.js'; import { config } from '../src/config.js'; import includes from 'core-js-pure/features/array/includes.js'; import { hook } from './hook.js'; +import {auctionManager} from './auctionManager.js'; const VIDEO_MEDIA_TYPE = 'video'; export const OUTSTREAM = 'outstream'; @@ -28,23 +29,21 @@ export const hasNonVideoBidder = adUnit => /** * Validate that the assets required for video context are present on the bid * @param {VideoBid} bid Video bid to validate - * @param {BidRequest[]} bidRequests All bid requests for an auction + * @param index * @return {Boolean} If object is valid */ -export function isValidVideoBid(bid, bidRequests) { - const bidRequest = getBidRequest(bid.requestId, bidRequests); - - const videoMediaType = - bidRequest && deepAccess(bidRequest, 'mediaTypes.video'); +export function isValidVideoBid(bid, {index = auctionManager.index} = {}) { + const videoMediaType = deepAccess(index.getMediaTypes(bid), 'video'); const context = videoMediaType && deepAccess(videoMediaType, 'context'); + const adUnit = index.getAdUnit(bid); // if context not defined assume default 'instream' for video bids // instream bids require a vast url or vast xml content - return checkVideoBidSetup(bid, bidRequest, videoMediaType, context); + return checkVideoBidSetup(bid, adUnit, videoMediaType, context); } -export const checkVideoBidSetup = hook('sync', function(bid, bidRequest, videoMediaType, context) { - if (!bidRequest || (videoMediaType && context !== OUTSTREAM)) { +export const checkVideoBidSetup = hook('sync', function(bid, adUnit, videoMediaType, context) { + if (videoMediaType && context !== OUTSTREAM) { // xml-only video bids require a prebid cache url if (!config.getConfig('cache.url') && bid.vastXml && !bid.vastUrl) { logError(` @@ -59,7 +58,7 @@ export const checkVideoBidSetup = hook('sync', function(bid, bidRequest, videoMe // outstream bids require a renderer on the bid or pub-defined on adunit if (context === OUTSTREAM) { - return !!(bid.renderer || bidRequest.renderer || videoMediaType.renderer); + return !!(bid.renderer || (adUnit && adUnit.renderer) || videoMediaType.renderer); } return true; diff --git a/src/videoCache.js b/src/videoCache.js index 9f1fd7e4117..219bca34726 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -11,7 +11,7 @@ import { ajax } from './ajax.js'; import { config } from './config.js'; -import * as utils from './utils.js'; +import {auctionManager} from './auctionManager.js'; /** * @typedef {object} CacheableUrlBid @@ -58,9 +58,11 @@ function wrapURI(uri, impUrl) { * the bid can't be converted cleanly. * * @param {CacheableBid} bid + * @param index */ -function toStorageRequest(bid) { +function toStorageRequest(bid, {index = auctionManager.index} = {}) { const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl); + const auction = index.getAuction(bid); let payload = { type: 'xml', @@ -71,10 +73,11 @@ function toStorageRequest(bid) { if (config.getConfig('cache.vasttrack')) { payload.bidder = bid.bidder; payload.bidid = bid.requestId; - // function has a thisArg set to bidderRequest for accessing the auctionStart - if (utils.isPlainObject(this) && this.hasOwnProperty('auctionStart')) { - payload.timestamp = this.auctionStart; - } + payload.aid = bid.auctionId; + } + + if (auction != null) { + payload.timestamp = auction.getAuctionStart(); } if (typeof bid.customCacheKey === 'string' && bid.customCacheKey !== '') { @@ -131,12 +134,11 @@ function shimStorageCallback(done) { * * @param {CacheableBid[]} bids A list of bid objects which should be cached. * @param {videoCacheStoreCallback} [done] An optional callback which should be executed after - * @param {BidderRequest} [bidderRequest] * the data has been stored in the cache. */ -export function store(bids, done, bidderRequest) { +export function store(bids, done) { const requestData = { - puts: bids.map(toStorageRequest, bidderRequest) + puts: bids.map(toStorageRequest) }; ajax(config.getConfig('cache.url'), shimStorageCallback(done), JSON.stringify(requestData), { diff --git a/test/.eslintrc.js b/test/.eslintrc.js index 842bccd99b1..abb34438653 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -1,40 +1,42 @@ module.exports = { - "env": { - "browser": true, - "mocha": true + env: { + browser: true, + mocha: true }, - "extends": "standard", - "globals": { - "$$PREBID_GLOBAL$$": false + extends: 'standard', + globals: { + '$$PREBID_GLOBAL$$': false }, - "parserOptions": { - "sourceType": "module" + parserOptions: { + sourceType: 'module', + ecmaVersion: 2018 }, - "rules": { - "comma-dangle": "off", - "semi": "off", - "space-before-function-paren": "off", + rules: { + 'comma-dangle': 'off', + semi: 'off', + 'space-before-function-paren': 'off', // Exceptions below this line are temporary, so that eslint can be added into the CI process. // Violations of these styles should be fixed, and the exceptions removed over time. // // See Issue #1111. - "camelcase": "off", - "eqeqeq": "off", - "no-mixed-spaces-and-tabs": "off", - "no-tabs": "off", - "no-unused-expressions": "off", - "import/no-duplicates": "off", - "no-template-curly-in-string": "off", - "no-global-assign": "off", - "no-path-concat": "off", - "no-redeclare": "off", - "node/no-deprecated-api": "off", - "no-return-assign": "off", - "no-undef": "off", - "no-unused-vars": "off", - "no-use-before-define": "off", - "no-useless-escape": "off", - "one-var": "off" + camelcase: 'off', + eqeqeq: 'off', + 'no-mixed-spaces-and-tabs': 'off', + 'no-tabs': 'off', + 'no-unused-expressions': 'off', + 'import/no-duplicates': 'off', + 'import/extensions': 'off', + 'no-template-curly-in-string': 'off', + 'no-global-assign': 'off', + 'no-path-concat': 'off', + 'no-redeclare': 'off', + 'node/no-deprecated-api': 'off', + 'no-return-assign': 'off', + 'no-undef': 'off', + 'no-unused-vars': 'off', + 'no-use-before-define': 'off', + 'no-useless-escape': 'off', + 'one-var': 'off' } }; diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index 908382f8daa..b0fbd7da806 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -1231,7 +1231,7 @@ export function getCurrencyRates() { }; } -export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, adUnitCode, adId, status, ttl, requestId}) { +export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, adUnitCode, adId, status, ttl, requestId, mediaType}) { let bid = { 'bidderCode': bidder, 'width': '300', @@ -1259,6 +1259,7 @@ export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, ad 'hb_pb': cpm, 'foobar': '300x250' }), + 'mediaType': mediaType, 'netRevenue': true, 'currency': 'USD', 'ttl': (!ttl) ? 300 : ttl diff --git a/test/helpers/indexStub.js b/test/helpers/indexStub.js new file mode 100644 index 00000000000..2916b60ae32 --- /dev/null +++ b/test/helpers/indexStub.js @@ -0,0 +1,28 @@ +import {AuctionIndex} from '../../src/auctionIndex.js'; + +export function stubAuctionIndex({bidRequests, bidderRequests, adUnits}) { + if (adUnits == null) { + adUnits = [] + } + if (bidderRequests == null) { + bidderRequests = [] + } + if (bidRequests != null) { + bidderRequests.push({ + bidderRequestId: 'mock-bidder-request', + bids: bidRequests + }); + } + const auction = { + getAuctionId() { + return 'mock-auction' + }, + getBidRequests() { + return bidderRequests; + }, + getAdUnits() { + return adUnits; + } + }; + return new AuctionIndex(() => ([auction])); +} diff --git a/test/helpers/prebidGlobal.js b/test/helpers/prebidGlobal.js index 597076ab0db..94776a5242b 100644 --- a/test/helpers/prebidGlobal.js +++ b/test/helpers/prebidGlobal.js @@ -1,3 +1,4 @@ window.$$PREBID_GLOBAL$$ = (window.$$PREBID_GLOBAL$$ || {}); +window.$$PREBID_GLOBAL$$.installedModules = (window.$$PREBID_GLOBAL$$.installedModules || []); window.$$PREBID_GLOBAL$$.cmd = window.$$PREBID_GLOBAL$$.cmd || []; window.$$PREBID_GLOBAL$$.que = window.$$PREBID_GLOBAL$$.que || []; diff --git a/test/helpers/syncPromise.js b/test/helpers/syncPromise.js new file mode 100644 index 00000000000..99361bd716e --- /dev/null +++ b/test/helpers/syncPromise.js @@ -0,0 +1,71 @@ +const orig = {}; +['resolve', 'reject', 'all', 'race', 'allSettled'].forEach((k) => orig[k] = Promise[k].bind(Promise)) + +// Callbacks attached through Promise.resolve(value).then(...) will usually +// not execute immediately even if `value` is immediately available. This +// breaks tests that were written before promises even though they are semantically still valid. +// They can be made to work by making promises quasi-synchronous. + +export function SyncPromise(value, fail = false) { + if (value instanceof SyncPromise) { + return value; + } else if (typeof value === 'object' && typeof value.then === 'function') { + return orig.resolve(value); + } else { + Object.assign(this, { + then: function (cb, err) { + const handler = fail ? err : cb; + if (handler != null) { + return new SyncPromise(handler(value)); + } else { + return this; + } + }, + catch: function (cb) { + if (fail) { + return new SyncPromise(cb(value)) + } else { + return this; + } + }, + finally: function (cb) { + cb(); + return this; + }, + __value: fail ? {status: 'rejected', reason: value} : {status: 'fulfilled', value} + }) + } +} + +Object.assign(SyncPromise, { + resolve: (val) => new SyncPromise(val), + reject: (val) => new SyncPromise(val, true), + race: (promises) => promises.find((p) => p instanceof SyncPromise) || orig.race(promises), + allSettled: (promises) => { + if (promises.every((p) => p instanceof SyncPromise)) { + return new SyncPromise(promises.map((p) => p.__value)) + } else { + return orig.allSettled(promises); + } + }, + all: (promises) => { + if (promises.every((p) => p instanceof SyncPromise)) { + return SyncPromise.allSettled(promises).then((result) => { + const err = result.find((r) => r.status === 'rejected'); + if (err != null) { + return new SyncPromise(err.reason, true); + } else { + return new SyncPromise(result.map((r) => r.value)) + } + }) + } else { + return orig.all(promises); + } + } +}) + +export function synchronizePromise(sandbox) { + Object.keys(orig).forEach((k) => { + sandbox.stub(window.Promise, k).callsFake(SyncPromise[k]); + }) +} diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index 2b36848bd8f..a4acb6fc414 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -9,6 +9,7 @@ const BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; const BID_WON = CONSTANTS.EVENTS.BID_WON; const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; const AD_RENDER_FAILED = CONSTANTS.EVENTS.AD_RENDER_FAILED; +const AD_RENDER_SUCCEEDED = CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED; const AUCTION_DEBUG = CONSTANTS.EVENTS.AUCTION_DEBUG; const ADD_AD_UNITS = CONSTANTS.EVENTS.ADD_AD_UNITS; @@ -86,6 +87,17 @@ FEATURE: Analytics Adapters API expect(result).to.deep.equal({args: {call: 'adRenderFailed'}, eventType: 'adRenderFailed'}); }); + it('SHOULD call global when a adRenderSucceeded event occurs', function () { + const eventType = AD_RENDER_SUCCEEDED; + const args = { call: 'adRenderSucceeded' }; + + adapter.enableAnalytics(); + events.emit(eventType, args); + + let result = JSON.parse(server.requests[0].requestBody); + expect(result).to.deep.equal({args: {call: 'adRenderSucceeded'}, eventType: 'adRenderSucceeded'}); + }); + it('SHOULD call global when an auction debug event occurs', function () { const eventType = AUCTION_DEBUG; const args = { call: 'auctionDebug' }; diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 69e34c4a07a..338d6ef5a46 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -4,6 +4,8 @@ import { AUCTION_COMPLETED, adjustBids, getMediaTypeGranularity, + getPriceByGranularity, + addBidResponse } from 'src/auction.js'; import CONSTANTS from 'src/constants.json'; import * as auctionModule from 'src/auction.js'; @@ -14,6 +16,12 @@ import * as store from 'src/videoCache.js'; import * as ajaxLib from 'src/ajax.js'; import find from 'core-js-pure/features/array/find.js'; import { server } from 'test/mocks/xhr.js'; +import {hook} from '../../src/hook.js'; +import {auctionManager} from '../../src/auctionManager.js'; +import 'src/debugging.js' // some tests look for debugging side effects +import {AuctionIndex} from '../../src/auctionIndex.js'; +import {expect} from 'chai'; +import {synchronizePromise} from '../helpers/syncPromise.js'; var assert = require('assert'); @@ -45,6 +53,7 @@ function mockBid(opts) { 'height': 250, 'bidderCode': bidderCode || BIDDER_CODE, 'requestId': utils.getUniqueIdentifierStr(), + 'transactionId': (opts && opts.transactionId) || ADUNIT_CODE, 'creativeId': 'id', 'currency': 'USD', 'netRevenue': true, @@ -75,7 +84,7 @@ function mockBidRequest(bid, opts) { return { 'bidderCode': bidderCode || bid.bidderCode, - 'auctionId': '20882439e3238c', + 'auctionId': opts && opts.auctionId, 'bidderRequestId': requestId, 'bids': [ { @@ -84,10 +93,11 @@ function mockBidRequest(bid, opts) { 'placementId': 'id' }, 'adUnitCode': adUnitCode || ADUNIT_CODE, + 'transactionId': bid.transactionId, 'sizes': [[300, 250], [300, 600]], 'bidId': bid.requestId, 'bidderRequestId': requestId, - 'auctionId': '20882439e3238c', + 'auctionId': opts && opts.auctionId, 'mediaTypes': mediaType } ], @@ -124,6 +134,31 @@ function mockAjaxBuilder() { } describe('auctionmanager.js', function () { + let indexAuctions, indexStub, promiseSandbox; + + before(() => { + // hooks are global and their side effects depend on what has been loaded + [ + auctionModule.addBidResponse, + auctionModule.addBidderRequests, + auctionModule.bidsBackCallback + ].forEach((h) => h.getHooks().remove()) + hook.ready(); + }); + + beforeEach(() => { + indexAuctions = []; + indexStub = sinon.stub(auctionManager, 'index'); + indexStub.get(() => new AuctionIndex(() => indexAuctions)); + promiseSandbox = sinon.createSandbox(); + synchronizePromise(promiseSandbox); + }); + + afterEach(() => { + indexStub.restore(); + promiseSandbox.restore(); + }); + describe('getKeyValueTargetingPairs', function () { const DEFAULT_BID = { cpm: 5.578, @@ -647,14 +682,14 @@ describe('auctionmanager.js', function () { }); }); - describe('addBidResponse', function () { + describe('addBidResponse #1', function () { let createAuctionStub; let adUnits; let adUnitCodes; let spec; let auction; let ajaxStub; - let bids = TEST_BIDS; + let bids; let makeRequestsStub; before(function () { @@ -666,19 +701,20 @@ describe('auctionmanager.js', function () { }); describe('when auction timeout is 3000', function () { - before(function () { - ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); - makeRequestsStub.returns(TEST_BID_REQS); - }); beforeEach(function () { + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); adUnits = [{ code: ADUNIT_CODE, + transactionId: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ] }]; adUnitCodes = [ADUNIT_CODE]; auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 3000}); + bids = TEST_BIDS.slice(); + makeRequestsStub.returns(bids.map(b => mockBidRequest(b, {auctionId: auction.getAuctionId()}))); + indexAuctions = [auction]; createAuctionStub = sinon.stub(auctionModule, 'newAuction'); createAuctionStub.returns(auction); @@ -687,11 +723,8 @@ describe('auctionmanager.js', function () { }); afterEach(function () { - auctionModule.newAuction.restore(); - }); - - after(function () { ajaxStub.restore(); + auctionModule.newAuction.restore(); }); function checkPbDg(cpm, expected, msg) { @@ -739,9 +772,7 @@ describe('auctionmanager.js', function () { url: 'renderer.js', render: (bid) => bid }; - let bidRequests = [Object.assign({}, TEST_BID_REQS[0])]; - bidRequests[0].bids[0] = Object.assign({ renderer }, bidRequests[0].bids[0]); - makeRequestsStub.returns(bidRequests); + Object.assign(adUnits[0], {renderer}); let bids1 = Object.assign({}, bids[0], @@ -762,9 +793,7 @@ describe('auctionmanager.js', function () { backupOnly: true, render: (bid) => bid }; - let bidRequests = [Object.assign({}, TEST_BID_REQS[0])]; - bidRequests[0].bids[0] = Object.assign({ renderer }, bidRequests[0].bids[0]); - makeRequestsStub.returns(bidRequests); + Object.assign(adUnits[0], {renderer}); let bids1 = Object.assign({}, bids[0], @@ -846,17 +875,19 @@ describe('auctionmanager.js', function () { url: 'renderer.js', render: (bid) => bid }; - + Object.assign(adUnits[0], {renderer}); // make sure that if the renderer is only on the second ad unit, prebid // still correctly uses it let bid = mockBid(); - let bidRequests = [mockBidRequest(bid)]; + let bidRequests = [mockBidRequest(bid, {auctionId: auction.getAuctionId()})]; bidRequests[0].bids[1] = Object.assign({ - renderer, bidId: utils.getUniqueIdentifierStr() }, bidRequests[0].bids[0]); - bidRequests[0].bids[0].adUnitCode = ADUNIT_CODE1; + Object.assign(bidRequests[0].bids[0], { + adUnitCode: ADUNIT_CODE1, + transactionId: ADUNIT_CODE1, + }); makeRequestsStub.returns(bidRequests); @@ -874,16 +905,16 @@ describe('auctionmanager.js', function () { describe('when auction timeout is 20', function () { let eventsEmitSpy; - before(function () { + function setupBids(auctionId) { bids = [mockBid(), mockBid({ bidderCode: BIDDER_CODE1 })]; - let bidRequests = bids.map(bid => mockBidRequest(bid)); - + let bidRequests = bids.map(bid => mockBidRequest(bid, {auctionId})); makeRequestsStub.returns(bidRequests); - }); + } beforeEach(function () { adUnits = [{ code: ADUNIT_CODE, + transactionId: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ] @@ -919,6 +950,7 @@ describe('auctionmanager.js', function () { done(); } auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: auctionCallback, cbTimeout: 20}); + setupBids(auction.getAuctionId()); auction.callBids(); respondToRequest(0); @@ -937,7 +969,7 @@ describe('auctionmanager.js', function () { done(); } auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: auctionCallback, cbTimeout: 20}); - + setupBids(auction.getAuctionId()); auction.callBids(); respondToRequest(0); respondToRequest(1); @@ -959,14 +991,14 @@ describe('auctionmanager.js', function () { done(); } auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: auctionCallback, cbTimeout: 20}); - + setupBids(auction.getAuctionId()); auction.callBids(); respondToRequest(0); }); }); }); - describe('addBidResponse', function () { + describe('addBidResponse #2', function () { let createAuctionStub; let adUnits; let adUnitCodes; @@ -978,38 +1010,32 @@ describe('auctionmanager.js', function () { let bids = TEST_BIDS; let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; - before(function () { - let bidRequests = [ - mockBidRequest(bids[0]), - mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }) - ]; - let makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests'); - makeRequestsStub.returns(bidRequests); - - ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); - }); - - after(function () { - ajaxStub.restore(); - adapterManager.makeBidRequests.restore(); - }); - beforeEach(function () { adUnits = [{ code: ADUNIT_CODE, + transactionId: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ] }, { code: ADUNIT_CODE1, + transactionId: ADUNIT_CODE1, bids: [ {bidder: BIDDER_CODE1, params: {placementId: 'id'}}, ] }]; adUnitCodes = adUnits.map(({ code }) => code); auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 3000}); + let bidRequests = [ + mockBidRequest(bids[0], { auctionId: auction.getAuctionId() }), + mockBidRequest(bids1[0], { auctionId: auction.getAuctionId(), adUnitCode: ADUNIT_CODE1 }) + ]; + let makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests'); + makeRequestsStub.returns(bidRequests); + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); createAuctionStub = sinon.stub(auctionModule, 'newAuction'); createAuctionStub.returns(auction); + indexAuctions = [auction]; spec = mockBidder(BIDDER_CODE, bids); spec1 = mockBidder(BIDDER_CODE1, bids1); @@ -1020,6 +1046,8 @@ describe('auctionmanager.js', function () { afterEach(function () { auctionModule.newAuction.restore(); + ajaxStub.restore(); + adapterManager.makeBidRequests.restore(); }); it('should not alter bid requestID', function () { @@ -1109,14 +1137,6 @@ describe('auctionmanager.js', function () { logInfoStub = sinon.stub(utils, 'logInfo'); logWarnStub = sinon.stub(utils, 'logWarn'); logErrorStub = sinon.stub(utils, 'logError'); - let bidRequests = [ - mockBidRequest(bids[0]), - mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }) - ]; - let makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests'); - makeRequestsStub.returns(bidRequests); - - ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); }); after(function () { @@ -1124,8 +1144,6 @@ describe('auctionmanager.js', function () { logInfoStub.restore(); logWarnStub.restore(); logErrorStub.restore(); - ajaxStub.restore(); - adapterManager.makeBidRequests.restore(); }); beforeEach(function () { @@ -1142,11 +1160,13 @@ describe('auctionmanager.js', function () { adUnits = [{ code: ADUNIT_CODE, + transactionId: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ] }, { code: ADUNIT_CODE1, + transactionId: ADUNIT_CODE1, bids: [ {bidder: BIDDER_CODE1, params: {placementId: 'id'}}, ] @@ -1155,6 +1175,15 @@ describe('auctionmanager.js', function () { auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 3000}); createAuctionStub = sinon.stub(auctionModule, 'newAuction'); createAuctionStub.returns(auction); + indexAuctions = [auction]; + let bidRequests = [ + mockBidRequest(bids[0], { auctionId: auction.getAuctionId() }), + mockBidRequest(bids1[0], { auctionId: auction.getAuctionId(), adUnitCode: ADUNIT_CODE1 }) + ]; + let makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests'); + makeRequestsStub.returns(bidRequests); + + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); spec = mockBidder(BIDDER_CODE, bids); spec1 = mockBidder(BIDDER_CODE1, bids1); @@ -1169,6 +1198,8 @@ describe('auctionmanager.js', function () { logWarnStub.resetHistory(); logErrorStub.resetHistory(); auctionModule.newAuction.restore(); + ajaxStub.restore(); + adapterManager.makeBidRequests.restore(); config.resetConfig(); }); @@ -1187,12 +1218,10 @@ describe('auctionmanager.js', function () { describe('getMediaTypeGranularity', function () { it('video', function () { - let bidReq = { - 'mediaTypes': { video: {id: '1'} } - }; + let mediaTypes = { video: {id: '1'} }; // mediaType is video and video.context is undefined - expect(getMediaTypeGranularity('video', bidReq, { + expect(getMediaTypeGranularity('video', mediaTypes, { banner: 'low', video: 'medium' })).to.equal('medium'); @@ -1208,10 +1237,8 @@ describe('auctionmanager.js', function () { })).to.equal('medium'); // also when mediaTypes.video is undefined - bidReq = { - 'mediaTypes': { banner: {} } - }; - expect(getMediaTypeGranularity('video', bidReq, { + mediaTypes = { banner: {} }; + expect(getMediaTypeGranularity('video', mediaTypes, { banner: 'low', video: 'medium' })).to.equal('medium'); @@ -1224,22 +1251,22 @@ describe('auctionmanager.js', function () { }); it('video-outstream', function () { - let bidReq = { 'mediaTypes': { video: { context: 'outstream' } } }; + let mediaTypes = { video: { context: 'outstream' } }; - expect(getMediaTypeGranularity('video', bidReq, { + expect(getMediaTypeGranularity('video', mediaTypes, { 'banner': 'low', 'video': 'medium', 'video-outstream': 'high' })).to.equal('high'); }); it('video-instream', function () { - let bidReq = { 'mediaTypes': { video: { context: 'instream' } } }; + let mediaTypes = { video: { context: 'instream' } }; - expect(getMediaTypeGranularity('video', bidReq, { + expect(getMediaTypeGranularity('video', mediaTypes, { banner: 'low', video: 'medium', 'video-instream': 'high' })).to.equal('high'); // fall back to video if video-instream not found - expect(getMediaTypeGranularity('video', bidReq, { + expect(getMediaTypeGranularity('video', mediaTypes, { banner: 'low', video: 'medium' })).to.equal('medium'); @@ -1249,43 +1276,83 @@ describe('auctionmanager.js', function () { }); it('native', function () { - expect(getMediaTypeGranularity('native', {mediaTypes: {native: {}}}, { + expect(getMediaTypeGranularity('native', {native: {}}, { banner: 'low', video: 'medium', native: 'high' })).to.equal('high'); }); }); + function mockAuction(getBidRequests, start = 1) { + return { + getBidRequests: getBidRequests, + getAdUnits: () => getBidRequests().flatMap(br => br.bids).map(br => ({ code: br.adUnitCode, transactionId: br.transactionId, mediaTypes: br.mediaTypes })), + getAuctionId: () => '1', + addBidReceived: () => true, + getTimeout: () => 1000, + getAuctionStart: () => start, + } + } + + describe('getPriceByGranularity', () => { + beforeEach(() => { + config.setConfig({ + mediaTypePriceGranularity: { + video: 'medium', + banner: 'low' + } + }); + }) + + afterEach(() => { + config.resetConfig(); + }) + + it('evaluates undef granularity on each call', () => { + const gpbg = getPriceByGranularity(); + expect(gpbg({ + mediaType: 'video', pbMg: 'medium' + }, { + 'mediaTypes': {video: {id: '1'}} + })).to.equal('medium'); + expect(gpbg({ + mediaType: 'banner', + pbLg: 'low' + }, { + 'mediaTypes': {banner: {}} + })).to.equal('low'); + }); + }) + describe('auctionCallbacks', function() { let bids = TEST_BIDS; let bidRequests; let doneSpy; - let auction = { - getBidRequests: () => bidRequests, - getAuctionId: () => '1', - addBidReceived: () => true, - getTimeout: () => 1000 - } + let auction; beforeEach(() => { + const start = Date.now(); + auction = mockAuction(() => bidRequests, start); + indexAuctions = [auction]; doneSpy = sinon.spy(); config.setConfig({ cache: { url: 'https://prebid.adnxs.com/pbc/v1/cache' } - }) + }); }); afterEach(() => { doneSpy.resetHistory(); config.resetConfig(); + bidRequests = null; }); it('should call auction done after bid is added to auction for mediaType banner', function () { let ADUNIT_CODE2 = 'adUnitCode2'; let BIDDER_CODE2 = 'sampleBidder2'; - let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; - let bids2 = [mockBid({ bidderCode: BIDDER_CODE2 })]; + let bids1 = [mockBid({ bidderCode: BIDDER_CODE1, transactionId: ADUNIT_CODE1 })]; + let bids2 = [mockBid({ bidderCode: BIDDER_CODE2, transactionId: ADUNIT_CODE2 })]; bidRequests = [ mockBidRequest(bids[0]), mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), @@ -1328,22 +1395,130 @@ describe('auctionmanager.js', function () { const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`; server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody); assert.equal(doneSpy.callCount, 1); - }) + }); + + describe('when addBidResponse hook returns promises', () => { + let resolvers, callbacks, bids; + + function hook(next, ...args) { + next.bail(new Promise((resolve, reject) => { + resolvers.resolve.push(resolve); + resolvers.reject.push(reject); + }).finally(() => next(...args))); + } + + function invokeCallbacks() { + bids.forEach((bid) => callbacks.addBidResponse(ADUNIT_CODE, bid)); + bidRequests.forEach(bidRequest => callbacks.adapterDone.call(bidRequest)); + } + + function delay(ms = 0) { + return new Promise((resolve) => { + setTimeout(resolve, ms) + }); + } + + beforeEach(() => { + promiseSandbox.restore(); + bids = [ + mockBid({bidderCode: BIDDER_CODE1}), + mockBid({bidderCode: BIDDER_CODE}) + ] + bidRequests = bids.map((b) => mockBidRequest(b)); + resolvers = {resolve: [], reject: []}; + addBidResponse.before(hook); + callbacks = auctionCallbacks(doneSpy, auction); + Object.assign(auction, { + addNoBid: sinon.spy() + }); + }); + + afterEach(() => { + addBidResponse.getHooks({hook: hook}).remove(); + }); + + it('should wait for bids without a request bids before calling auctionDone', () => { + callbacks.addBidResponse(ADUNIT_CODE, Object.assign(mockBid(), {requestId: null})); + invokeCallbacks(); + resolvers.resolve.slice(1, 3).forEach((fn) => fn()); + return delay().then(() => { + expect(doneSpy.called).to.be.false; + resolvers.resolve[0](); + return delay(); + }).then(() => { + expect(doneSpy.called).to.be.true; + }); + }); + + Object.entries({ + 'all succeed': ['resolve', 'resolve'], + 'some fail': ['resolve', 'reject'], + 'all fail': ['reject', 'reject'] + }).forEach(([test, results]) => { + describe(`(and ${test})`, () => { + it('should wait for them to complete before calling auctionDone', () => { + invokeCallbacks(); + return delay().then(() => { + expect(doneSpy.called).to.be.false; + expect(auction.addNoBid.called).to.be.false; + resolvers[results[0]][0](); + return delay(); + }).then(() => { + expect(doneSpy.called).to.be.false; + expect(auction.addNoBid.called).to.be.false; + resolvers[results[1]][1](); + return delay(); + }).then(() => { + expect(doneSpy.called).to.be.true; + }); + }); + }); + }); + + Object.entries({ + bidder: (timeout) => { + bidRequests.forEach((r) => r.timeout = timeout); + auction.getTimeout = () => timeout + 10000 + }, + auction: (timeout) => { + auction.getTimeout = () => timeout; + bidRequests.forEach((r) => r.timeout = timeout + 10000) + } + }).forEach(([test, setTimeout]) => { + it(`should respect ${test} timeout if they never complete`, () => { + const start = Date.now() - 2900; + auction.getAuctionStart = () => start; + setTimeout(3000); + invokeCallbacks(); + return delay().then(() => { + expect(doneSpy.called).to.be.false; + return delay(100); + }).then(() => { + expect(doneSpy.called).to.be.true; + }); + }); + + it(`should not wait if ${test} has already timed out`, () => { + const start = Date.now() - 2000; + auction.getAuctionStart = () => start; + setTimeout(1000); + invokeCallbacks(); + return delay().then(() => { + expect(doneSpy.called).to.be.true; + }); + }); + }) + }); }); describe('auctionOptions', function() { let bidRequests; let doneSpy; let clock; - let auction = { - getBidRequests: () => bidRequests, - getAuctionId: () => '1', - addBidReceived: () => true, - getTimeout: () => 1000 - } let requiredBidder = BIDDER_CODE; let requiredBidder1 = BIDDER_CODE1; let secondaryBidder = 'doNotWaitForMe'; + let auction; beforeEach(() => { clock = sinon.useFakeTimers(); @@ -1352,7 +1527,11 @@ describe('auctionmanager.js', function () { 'auctionOptions': { secondaryBidders: [ secondaryBidder ] } - }) + }); + + const start = Date.now(); + auction = mockAuction(() => bidRequests); + indexAuctions = [auction]; }); afterEach(() => { @@ -1362,9 +1541,9 @@ describe('auctionmanager.js', function () { }); it('should not wait to call auction done for secondary bidders', function () { - let bids1 = [mockBid({ bidderCode: requiredBidder })]; - let bids2 = [mockBid({ bidderCode: requiredBidder1 })]; - let bids3 = [mockBid({ bidderCode: secondaryBidder })]; + let bids1 = [mockBid({ bidderCode: requiredBidder, transactionId: ADUNIT_CODE1 })]; + let bids2 = [mockBid({ bidderCode: requiredBidder1, transactionId: ADUNIT_CODE1 })]; + let bids3 = [mockBid({ bidderCode: secondaryBidder, transactionId: ADUNIT_CODE1 })]; bidRequests = [ mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }), diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 8161d9f1827..88d6e61c706 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -6,19 +6,27 @@ const utils = require('src/utils'); let getConfig; let setConfig; +let readConfig; +let mergeConfig; let getBidderConfig; let setBidderConfig; +let mergeBidderConfig; let setDefaults; describe('config API', function () { let logErrorSpy; let logWarnSpy; + let config; + beforeEach(function () { - const config = newConfig(); + config = newConfig(); getConfig = config.getConfig; setConfig = config.setConfig; + readConfig = config.readConfig; + mergeConfig = config.mergeConfig; getBidderConfig = config.getBidderConfig; setBidderConfig = config.setBidderConfig; + mergeBidderConfig = config.mergeBidderConfig; setDefaults = config.setDefaults; logErrorSpy = sinon.spy(utils, 'logError'); logWarnSpy = sinon.spy(utils, 'logWarn'); @@ -37,6 +45,67 @@ describe('config API', function () { expect(getConfig()).to.be.a('object'); }); + it('readConfig returns deepCopy of the internal config object', function () { + setConfig({ foo: {biz: 'bar'} }); + const config1 = readConfig('foo'); + config1.biz = 'buz'; + const config2 = readConfig('foo'); + expect(readConfig()).to.be.a('object'); + expect(config1.biz).to.not.equal(config2.biz); + }); + + it('readConfig retrieves arbitrary configuration properties', function () { + setConfig({ baz: 'qux' }); + expect(readConfig('baz')).to.equal('qux'); + }); + + it('readConfig has subscribe functionality for adding listeners to config updates', function () { + const listener = sinon.spy(); + + readConfig(listener); + + setConfig({ foo: 'bar' }); + + sinon.assert.calledOnce(listener); + sinon.assert.calledWith(listener, { foo: 'bar' }); + }); + + it('readConfig subscribers can unsubscribe', function () { + const listener = sinon.spy(); + + const unsubscribe = getConfig(listener); + + unsubscribe(); + + readConfig({ logging: true }); + + sinon.assert.notCalled(listener); + }); + + it('readConfig subscribers can subscribe to topics', function () { + const listener = sinon.spy(); + + readConfig('logging', listener); + + setConfig({ logging: true, foo: 'bar' }); + + sinon.assert.calledOnce(listener); + sinon.assert.calledWithExactly(listener, { logging: true }); + }); + + it('readConfig topic subscribers are only called when that topic is changed', function () { + const listener = sinon.spy(); + const wildcard = sinon.spy(); + + readConfig('subject', listener); + readConfig(wildcard); + + setConfig({ foo: 'bar' }); + + sinon.assert.notCalled(listener); + sinon.assert.calledOnce(wildcard); + }); + it('sets and gets arbitrary configuration properties', function () { setConfig({ baz: 'qux' }); expect(getConfig('baz')).to.equal('qux'); @@ -233,7 +302,7 @@ describe('config API', function () { expect(logWarnSpy.called).to.equal(false); }); - it('sets auctionOptions', function () { + it('sets auctionOptions secondaryBidders', function () { const auctionOptionsConfig = { 'secondaryBidders': ['rubicon', 'appnexus'] } @@ -241,6 +310,14 @@ describe('config API', function () { expect(getConfig('auctionOptions')).to.eql(auctionOptionsConfig); }); + it('sets auctionOptions suppressStaleRender', function () { + const auctionOptionsConfig = { + 'suppressStaleRender': true + } + setConfig({ auctionOptions: auctionOptionsConfig }); + expect(getConfig('auctionOptions')).to.eql(auctionOptionsConfig); + }); + it('should log warning for the wrong value passed to auctionOptions', function () { setConfig({ auctionOptions: '' }); expect(logWarnSpy.calledOnce).to.equal(true); @@ -257,6 +334,15 @@ describe('config API', function () { assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged'); }); + it('should log warning for invalid auctionOptions suppress stale render', function () { + setConfig({ auctionOptions: { + 'suppressStaleRender': 'test', + }}); + expect(logWarnSpy.calledOnce).to.equal(true); + const warning = 'Auction Options suppressStaleRender must be of type boolean'; + assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged'); + }); + it('should log warning for invalid properties to auctionOptions', function () { setConfig({ auctionOptions: { 'testing': true @@ -265,4 +351,300 @@ describe('config API', function () { const warning = 'Auction Options given an incorrect param: testing'; assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged'); }); + + it('should merge input with existing global config', function () { + const obj = { + ortb2: { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + } + } + }; + setConfig({ ortb2: { + user: { + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + }); + mergeConfig(obj); + const expected = { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + }, + user: { + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + expect(getConfig('ortb2')).to.deep.equal(expected); + }); + + it('input should take precedence over existing config if keys are the same', function() { + const input = { + ortb2: { + user: { + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + } + setConfig({ ortb2: { + user: { + ext: { + data: { + registered: false + } + } + } + }}); + mergeConfig(input); + const expected = { + user: { + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + expect(getConfig('ortb2')).to.deep.equal(expected); + }); + + it('should log error for a non-object value passed in', function () { + mergeConfig('invalid object'); + expect(logErrorSpy.calledOnce).to.equal(true); + const error = 'mergeConfig input must be an object'; + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + }); + + it('should merge input with existing bidder config', function () { + const input = { + bidders: ['rubicon', 'appnexus'], + config: { + ortb2: { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + }, + user: { + ext: { + ssp: 'magnite', + data: { + registered: false, + interests: ['sports'] + } + } + } + } + } + }; + setBidderConfig({ + bidders: ['rubicon'], + config: { + ortb2: { + user: { + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + } + }); + mergeBidderConfig(input); + const expected = { + rubicon: { + ortb2: { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + }, + user: { + ext: { + ssp: 'magnite', + data: { + registered: false, + interests: ['cars', 'sports'] + } + } + } + } + }, + appnexus: { + ortb2: { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + }, + user: { + ext: { + ssp: 'magnite', + data: { + registered: false, + interests: ['sports'] + } + } + } + } + } + } + expect(getBidderConfig()).to.deep.equal(expected); + }); + + it('should log error for a non-object value passed in', function () { + mergeBidderConfig('invalid object'); + expect(logErrorSpy.calledOnce).to.equal(true); + const error = 'setBidderConfig bidder options must be an object'; + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + }); + + it('should log error for empty bidders array', function () { + mergeBidderConfig({ + bidders: [], + config: { + ortb2: { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + } + } + } + }); + expect(logErrorSpy.calledOnce).to.equal(true); + const error = 'setBidderConfig bidder options must contain a bidders list with at least 1 bidder'; + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + }); + + it('should log error for nonexistent config object', function () { + mergeBidderConfig({ + bidders: ['appnexus'] + }); + expect(logErrorSpy.calledOnce).to.equal(true); + const error = 'setBidderConfig bidder options must contain a config object'; + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + }); + + it('should merge without array duplication', function() { + const userObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + const userObj2 = { + name: 'www.dataprovider2.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1914' + }] + }; + + const siteObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1812' + }, + { + id: '1955' + } + ] + } + + setConfig({ + ortb2: { + user: { + data: [userObj1, userObj2] + }, + site: { + content: { + data: [siteObj1] + } + } + } + }); + + const rtd = { + ortb2: { + user: { + data: [userObj1] + }, + site: { + content: { + data: [siteObj1] + } + } + } + }; + mergeConfig(rtd); + + let ortb2Config = getConfig('ortb2'); + + expect(ortb2Config.user.data).to.deep.include.members([userObj1, userObj2]); + expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1]); + expect(ortb2Config.user.data).to.have.lengthOf(2); + expect(ortb2Config.site.content.data).to.have.lengthOf(1); + }); + + it('should not corrupt global configuration with bidder configuration', () => { + // https://github.com/prebid/Prebid.js/issues/7956 + config.setConfig({ + outer: { + inner: ['global'] + } + }); + config.setBidderConfig({ + bidders: ['bidder'], + config: { + outer: { + inner: ['bidder'] + } + } + }); + config.runWithBidder('bidder', () => config.getConfig()) + expect(config.getConfig('outer')).to.eql({ + inner: ['global'] + }); + }); }); diff --git a/test/spec/debugging_spec.js b/test/spec/debugging_spec.js index bf49a579cbd..b0f78243e4a 100644 --- a/test/spec/debugging_spec.js +++ b/test/spec/debugging_spec.js @@ -3,10 +3,15 @@ import { expect } from 'chai'; import { sessionLoader, addBidResponseHook, addBidderRequestsHook, getConfig, disableOverrides, addBidResponseBound, addBidderRequestsBound } from 'src/debugging.js'; import { addBidResponse, addBidderRequests } from 'src/auction.js'; import { config } from 'src/config.js'; +import {hook} from '../../src/hook.js'; describe('bid overrides', function () { let sandbox; + before(() => { + hook.ready(); + }); + beforeEach(function () { sandbox = sinon.sandbox.create(); }); @@ -109,8 +114,14 @@ describe('bid overrides', function () { }); expect(bids.length).to.equal(2); - expect(bids[0].cpm).to.equal(2); - expect(bids[1].cpm).to.equal(2); + sinon.assert.match(bids[0], { + cpm: 2, + isDebug: true, + }) + sinon.assert.match(bids[1], { + cpm: 2, + isDebug: true, + }); }); it('should allow us to override bids by bidder', function () { @@ -123,8 +134,14 @@ describe('bid overrides', function () { }); expect(bids.length).to.equal(2); - expect(bids[0].cpm).to.equal(2); - expect(bids[1].cpm).to.equal(0.5); + sinon.assert.match(bids[0], { + cpm: 2, + isDebug: true + }); + sinon.assert.match(bids[1], { + cpm: 0.5, + isDebug: sinon.match.falsy + }) }); it('should allow us to override bids by adUnitCode', function () { @@ -139,8 +156,14 @@ describe('bid overrides', function () { }); expect(bids.length).to.equal(2); - expect(bids[0].cpm).to.equal(0.5); - expect(bids[1].cpm).to.equal(2); + sinon.assert.match(bids[0], { + cpm: 0.5, + isDebug: sinon.match.falsy, + }); + sinon.assert.match(bids[1], { + cpm: 2, + isDebug: true, + }); }); }); diff --git a/test/spec/integration/faker/fixtures.js b/test/spec/integration/faker/fixtures.js index a11bd126d61..6a65da37d37 100644 --- a/test/spec/integration/faker/fixtures.js +++ b/test/spec/integration/faker/fixtures.js @@ -23,7 +23,7 @@ export function makeBidder(overrides = {}) { bidder: `${faker.company.bsBuzz()}Media`, params: { abc: faker.random.alphaNumeric(10), - xyz: faker.random.number({ max: 10, precision: 2 }) + xyz: faker.datatype.number({ max: 10, precision: 2 }) }, callBids: sinon.spy() }, overrides); @@ -39,4 +39,4 @@ export function makeRequest(overrides = {}) { }, overrides); } -export function randomFive() { return faker.random.number({ min: 10000, max: 99999 }); } +export function randomFive() { return faker.datatype.number({ min: 10000, max: 99999 }); } diff --git a/test/spec/integration/faker/googletag.js b/test/spec/integration/faker/googletag.js index 9d91bf315d9..a8676b500a5 100644 --- a/test/spec/integration/faker/googletag.js +++ b/test/spec/integration/faker/googletag.js @@ -51,9 +51,15 @@ export function enable() { window.googletag = { _slots: [], _callbackMap: {}, + _ppid: undefined, + cmd: [], pubads: function () { var self = this; return { + setPublisherProvidedId: function (ppid) { + self._ppid = ppid; + }, + getSlots: function () { return self._slots; }, @@ -91,4 +97,9 @@ export function disable() { window.googletag = undefined; } +export function reset() { + disable(); + enable(); +} + enable(); diff --git a/test/spec/modules/microadBidAdapter_spec.js b/test/spec/microadBidAdapter_spec.js similarity index 86% rename from test/spec/modules/microadBidAdapter_spec.js rename to test/spec/microadBidAdapter_spec.js index 8298e2bd559..be310fb8e3c 100644 --- a/test/spec/modules/microadBidAdapter_spec.js +++ b/test/spec/microadBidAdapter_spec.js @@ -265,6 +265,36 @@ describe('microadBidAdapter', () => { expect(request.url.lastIndexOf('https', 0) === 0).to.be.true; }); }); + + it('should add Liveramp identity link if it is available in request parameters', () => { + const bidRequestWithLiveramp = Object.assign({}, bidRequestTemplate, { + userId: {idl_env: 'idl-env-sample'} + }); + const requests = spec.buildRequests([bidRequestWithLiveramp], bidderRequest) + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + idl_env: 'idl-env-sample' + }) + ); + }) + }); + + it('should not add Liveramp identity link if it is not available in request parameters', () => { + const bidRequestWithLiveramp = Object.assign({}, bidRequestTemplate, { + userId: {} + }); + const requests = spec.buildRequests([bidRequestWithLiveramp], bidderRequest) + const expectedResult = Object.assign({}, expectedResultTemplate) + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt + }) + ); + }) + }); }); describe('interpretResponse', () => { @@ -278,7 +308,10 @@ describe('microadBidAdapter', () => { ttl: 10, creativeId: 'creative-id', netRevenue: true, - currency: 'JPY' + currency: 'JPY', + meta: { + advertiserDomains: ['foobar.com'] + } } }; const expectedBidResponseTemplate = { @@ -290,7 +323,10 @@ describe('microadBidAdapter', () => { ttl: 10, creativeId: 'creative-id', netRevenue: true, - currency: 'JPY' + currency: 'JPY', + meta: { + advertiserDomains: ['foobar.com'] + } }; it('should return nothing if server response body does not contain cpm', () => { @@ -324,6 +360,16 @@ describe('microadBidAdapter', () => { expect(spec.interpretResponse(serverResponseWithDealId)).to.deep.equal([expectedBidResponse]); }); + + it('should return a valid bidResponse without meta if serverResponse is valid, has a nonzero cpm and no deal id', () => { + const serverResponseWithoutMeta = Object.assign({}, utils.deepClone(serverResponseTemplate)); + delete serverResponseWithoutMeta.body.meta; + const expectedBidResponse = Object.assign({}, expectedBidResponseTemplate, { + meta: { advertiserDomains: [] } + }); + + expect(spec.interpretResponse(serverResponseWithoutMeta)).to.deep.equal([expectedBidResponse]); + }); }); describe('getUserSyncs', () => { diff --git a/test/spec/modules/1ad4goodBidAdapter_spec.js b/test/spec/modules/1ad4goodBidAdapter_spec.js deleted file mode 100644 index b9cd86a4cf7..00000000000 --- a/test/spec/modules/1ad4goodBidAdapter_spec.js +++ /dev/null @@ -1,548 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/1ad4goodBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import * as bidderFactory from 'src/adapters/bidderFactory.js'; -import { deepClone } from 'src/utils.js'; -import { config } from 'src/config.js'; - -const ENDPOINT = 'https://hb.1ad4good.org/prebid'; - -describe('AdforgoodAdapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': '1ad4good', - 'params': { - 'placementId': '10433394' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'placementId': 0 - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let bidRequests = [ - { - 'bidder': '1ad4good', - 'params': { - 'placementId': '10433394' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - it('should parse out private sizes', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - privateSizes: [300, 250] - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.tags[0].private_sizes).to.exist; - expect(payload.tags[0].private_sizes).to.deep.equal([{width: 300, height: 250}]); - }); - - it('should add source and verison to the tag', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.sdk).to.exist; - expect(payload.sdk).to.deep.equal({ - source: 'pbjs', - version: '$prebid.version$' - }); - }); - - it('should populate the ad_types array on all requests', function () { - ['banner', 'video'].forEach(type => { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes[type] = {}; - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.tags[0].ad_types).to.deep.equal([type]); - }); - }); - - it('should populate the ad_types array on outstream requests', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes.video = {context: 'outstream'}; - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.tags[0].ad_types).to.deep.equal(['video']); - }); - - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(bidRequests); - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('POST'); - }); - - it('should attach valid video params to the tag', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - video: { - id: 123, - minduration: 100, - foobar: 'invalid' - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - id: 123, - minduration: 100 - }); - }); - - it('should add video property when adUnit includes a renderer', function () { - const videoData = { - mediaTypes: { - video: { - context: 'outstream', - mimes: ['video/mp4'] - } - }, - params: { - placementId: '10433394', - video: { - skippable: true, - playback_method: ['auto_play_sound_off'] - } - } - }; - - let bidRequest1 = deepClone(bidRequests[0]); - bidRequest1 = Object.assign({}, bidRequest1, videoData, { - renderer: { - url: 'http://test.renderer.url', - render: function () {} - } - }); - - let bidRequest2 = deepClone(bidRequests[0]); - bidRequest2.adUnitCode = 'adUnit_code_2'; - bidRequest2 = Object.assign({}, bidRequest2, videoData); - - const request = spec.buildRequests([bidRequest1, bidRequest2]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - skippable: true, - playback_method: ['auto_play_sound_off'], - custom_renderer_present: true - }); - expect(payload.tags[1].video).to.deep.equal({ - skippable: true, - playback_method: ['auto_play_sound_off'] - }); - }); - - it('should attach valid user params to the tag', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - user: { - externalUid: '123', - foobar: 'invalid' - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.user).to.exist; - expect(payload.user).to.deep.equal({ - externalUid: '123', - }); - }); - - // it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { - // let bidRequest = Object.assign({}, - // bidRequests[0], - // { - // mediaType: 'native', - // nativeParams: { - // image: { required: true } - // } - // } - // ); - // bidRequest.sizes = [[150, 100], [300, 250]]; - - // let request = spec.buildRequests([bidRequest]); - // let payload = JSON.parse(request.data); - // expect(payload.tags[0].sizes).to.deep.equal([{width: 150, height: 100}, {width: 300, height: 250}]); - - // delete bidRequest.sizes; - - // request = spec.buildRequests([bidRequest]); - // payload = JSON.parse(request.data); - - // expect(payload.tags[0].sizes).to.deep.equal([{width: 1, height: 1}]); - // }); - - it('should convert keyword params to proper form and attaches to request', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - keywords: { - single: 'val', - singleArr: ['val'], - singleArrNum: [5], - multiValMixed: ['value1', 2, 'value3'], - singleValNum: 123, - emptyStr: '', - emptyArr: [''], - badValue: {'foo': 'bar'} // should be dropped - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.tags[0].keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'singleArrNum', - 'value': ['5'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'singleValNum', - 'value': ['123'] - }, { - 'key': 'emptyStr' - }, { - 'key': 'emptyArr' - }]); - }); - - it('should add payment rules to the request', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - usePaymentRule: true - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.tags[0].use_pmt_rule).to.equal(true); - }); - - it('should add gdpr consent information to the request', function () { - let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - let bidderRequest = { - 'bidderCode': '1ad4good', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - gdprApplies: true - } - }; - bidderRequest.bids = bidRequests; - - const request = spec.buildRequests(bidRequests, bidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.gdpr_consent).to.exist; - expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString); - expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; - }); - - it('supports sending hybrid mobile app parameters', function () { - let appRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - app: { - id: 'B1O2W3M4AN.com.prebid.webview', - geo: { - lat: 40.0964439, - lng: -75.3009142 - }, - device_id: { - idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', // Apple advertising identifier - aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', // Android advertising identifier - md5udid: '5756ae9022b2ea1e47d84fead75220c8', // MD5 hash of the ANDROID_ID - sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', // SHA1 hash of the ANDROID_ID - windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' // Windows advertising identifier - } - } - } - } - ); - const request = spec.buildRequests([appRequest]); - const payload = JSON.parse(request.data); - expect(payload.app).to.exist; - expect(payload.app).to.deep.equal({ - appid: 'B1O2W3M4AN.com.prebid.webview' - }); - expect(payload.device.device_id).to.exist; - expect(payload.device.device_id).to.deep.equal({ - aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', - idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', - md5udid: '5756ae9022b2ea1e47d84fead75220c8', - sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', - windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' - }); - expect(payload.device.geo).to.exist; - expect(payload.device.geo).to.deep.equal({ - lat: 40.0964439, - lng: -75.3009142 - }); - }); - - it('should add referer info to payload', function () { - const bidRequest = Object.assign({}, bidRequests[0]) - const bidderRequest = { - refererInfo: { - referer: 'http://example.com/page.html', - reachedTop: true, - numIframes: 2, - stack: [ - 'http://example.com/page.html', - 'http://example.com/iframe1.html', - 'http://example.com/iframe2.html' - ] - } - } - const request = spec.buildRequests([bidRequest], bidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.referrer_detection).to.exist; - expect(payload.referrer_detection).to.deep.equal({ - rd_ref: 'http%3A%2F%2Fexample.com%2Fpage.html', - rd_top: true, - rd_ifs: 2, - rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') - }); - }); - }) - - describe('interpretResponse', function () { - let bfStub; - before(function() { - bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); - }); - - after(function() { - bfStub.restore(); - }); - - let response = { - 'version': '3.0.0', - 'tags': [ - { - 'uuid': '3db3773286ee59', - 'tag_id': 10433394, - 'auction_id': '4534722592064951574', - 'nobid': false, - 'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad', - 'timeout_ms': 10000, - 'ad_profile_id': 27079, - 'ads': [ - { - 'content_source': 'rtb', - 'ad_type': 'banner', - 'buyer_member_id': 958, - 'creative_id': 29681110, - 'media_type_id': 1, - 'media_subtype_id': 1, - 'cpm': 0.5, - 'cpm_publisher_currency': 0.5, - 'publisher_currency_code': '$', - 'client_initiated_ad_counting': true, - 'viewability': { - 'config': '' - }, - 'rtb': { - 'banner': { - 'content': '', - 'width': 300, - 'height': 250 - }, - 'trackers': [ - { - 'impression_urls': [ - 'http://lax1-ib.adnxs.com/impression' - ], - 'video_events': {} - } - ] - } - } - ] - } - ] - }; - - it('should get correct bid response', function () { - let expectedResponse = [ - { - 'requestId': '3db3773286ee59', - 'cpm': 0.5, - 'creativeId': 29681110, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '', - 'mediaType': 'banner', - 'currency': 'USD', - 'ttl': 300, - 'netRevenue': true, - 'adUnitCode': 'code', - 'ads4good': { - 'buyerMemberId': 958 - } - } - ]; - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); - }); - - it('handles nobid responses', function () { - let response = { - 'version': '0.0.1', - 'tags': [{ - 'uuid': '84ab500420319d', - 'tag_id': 5976557, - 'auction_id': '297492697822162468', - 'nobid': true - }] - }; - let bidderRequest; - - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result.length).to.equal(0); - }); - - it('handles non-banner media responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'content': '' - } - }, - 'javascriptTrackers': '' - }] - }] - }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code' - }] - } - - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); - - it('should add deal_priority and deal_code', function() { - let responseWithDeal = deepClone(response); - responseWithDeal.tags[0].ads[0].deal_priority = 'high'; - responseWithDeal.tags[0].ads[0].deal_code = '123'; - - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); - expect(Object.keys(result[0].ads4good)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); - }); - - it('should add advertiser id', function() { - let responseAdvertiserId = deepClone(response); - responseAdvertiserId.tags[0].ads[0].advertiser_id = '123'; - - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); - expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); - }) - }); -}); diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index b5443cdd5c2..141edc1e61c 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -16,20 +16,21 @@ function validateBuiltServerRequest(builtReq, expectedReq) { describe('33acrossBidAdapter:', function () { const BIDDER_CODE = '33across'; const SITE_ID = 'sample33xGUID123456789'; - const PRODUCT_ID = 'siab'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; let element, win; let bidRequests; let sandbox; - function TtxRequestBuilder() { + function TtxRequestBuilder(siteId = SITE_ID) { const ttxRequest = { - imp: [{}], + imp: [{ + id: 'b1' + }], site: { - id: SITE_ID + id: siteId }, - id: 'b1', + id: 'r1', regs: { ext: { gdpr: 0 @@ -46,66 +47,83 @@ describe('33acrossBidAdapter:', function () { } }; + this.addImp = (id = 'b2') => { + ttxRequest.imp.push({ id }); + + return this; + } + this.withBanner = () => { - Object.assign(ttxRequest.imp[0], { - banner: { - format: [ - { - w: 300, - h: 250 - }, - { - w: 728, - h: 90 - } - ], - ext: { - ttx: { - viewability: { - amount: 100 + ttxRequest.imp.forEach((imp) => { + Object.assign(imp, { + banner: { + format: [ + { + w: 300, + h: 250 + }, + { + w: 728, + h: 90 + } + ], + ext: { + ttx: { + viewability: { + amount: 100 + } } } } - } + }); }); - return this; }; this.withBannerSizes = this.withSizes = sizes => { - Object.assign(ttxRequest.imp[0].banner, { format: sizes }); + ttxRequest.imp.forEach((imp) => { + Object.assign(imp.banner, { format: sizes }); + }); + return this; }; this.withVideo = (params = {}) => { - Object.assign(ttxRequest.imp[0], { - video: { - w: 300, - h: 250, - placement: 2, - ...params - } + ttxRequest.imp.forEach((imp) => { + Object.assign(imp, { + video: { + w: 300, + h: 250, + placement: 2, + ...params + } + }); }); return this; }; this.withViewability = (viewability, format = 'banner') => { - Object.assign(ttxRequest.imp[0][format], { - ext: { - ttx: { viewability } - } + ttxRequest.imp.forEach((imp) => { + Object.assign(imp[format], { + ext: { + ttx: { viewability } + } + }); }); + return this; }; - this.withProduct = (prod = PRODUCT_ID) => { - Object.assign(ttxRequest.imp[0], { - ext: { - ttx: { - prod + this.withProduct = (prod = 'siab') => { + ttxRequest.imp.forEach((imp) => { + Object.assign(imp, { + ext: { + ttx: { + prod + } } - } + }); }); return this; @@ -249,7 +267,7 @@ describe('33acrossBidAdapter:', function () { bidderRequestId: 'b1a', params: { siteId: SITE_ID, - productId: PRODUCT_ID + productId: 'siab' }, adUnitCode: 'div-id', auctionId: 'r1', @@ -258,35 +276,61 @@ describe('33acrossBidAdapter:', function () { } ]; + this.addBid = (bidParams = {}) => { + bidRequests.push({ + bidId: 'b2', + bidder: '33across', + bidderRequestId: 'b1b', + params: { + siteId: SITE_ID, + productId: 'siab' + }, + adUnitCode: 'div-id', + auctionId: 'r1', + mediaTypes: {}, + transactionId: 't2', + ...bidParams + }); + + return this; + }; + this.withBanner = () => { - bidRequests[0].mediaTypes.banner = { - sizes: [ - [300, 250], - [728, 90] - ] - }; + bidRequests.forEach((bid) => { + bid.mediaTypes.banner = { + sizes: [ + [300, 250], + [728, 90] + ] + }; + }); return this; }; this.withProduct = (prod) => { - bidRequests[0].params.productId = prod; - + bidRequests.forEach((bid) => { + bid.params.productId = prod; + }); return this; }; this.withVideo = (params) => { - bidRequests[0].mediaTypes.video = { - playerSize: [[300, 250]], - context: 'outstream', - ...params - }; + bidRequests.forEach((bid) => { + bid.mediaTypes.video = { + playerSize: [[300, 250]], + context: 'outstream', + ...params + }; + }); return this; } this.withUserIds = (eids) => { - bidRequests[0].userIdAsEids = eids; + bidRequests.forEach((bid) => { + bid.userIdAsEids = eids; + }); return this; }; @@ -315,6 +359,7 @@ describe('33acrossBidAdapter:', function () { } }; win = { + parent: null, document: { visibilityState: 'visible' }, @@ -331,7 +376,7 @@ describe('33acrossBidAdapter:', function () { sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1); - sandbox.stub(document, 'getElementById').withArgs('div-id').returns(element); + sandbox.stub(document, 'getElementById').returns(element); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); }); @@ -1376,10 +1421,146 @@ describe('33acrossBidAdapter:', function () { }); }); }); + + context('when SRA mode is enabled', function() { + it('builds a single request with multiple imps corresponding to each group {siteId, productId}', function() { + sandbox.stub(config, 'getConfig').callsFake(() => { + return { + enableSRAMode: true + } + }); + + const bidRequests = new BidRequestsBuilder() + .addBid() + .addBid({ + bidId: 'b3', + adUnitCode: 'div-id', + params: { + siteId: 'sample33xGUID123456780', + productId: 'siab' + } + }) + .addBid({ + bidId: 'b4', + adUnitCode: 'div-id', + params: { + siteId: 'sample33xGUID123456780', + productId: 'inview' + } + }) + .withBanner() + .withVideo({context: 'outstream'}) + .build(); + + const req1 = new TtxRequestBuilder() + .addImp() + .withProduct('siab') + .withBanner() + .withVideo() + .build(); + + const req2 = new TtxRequestBuilder('sample33xGUID123456780') + .withProduct('siab') + .withBanner() + .withVideo() + .build(); + + req2.imp[0].id = 'b3'; + + const req3 = new TtxRequestBuilder('sample33xGUID123456780') + .withProduct('inview') + .withBanner() + .withVideo() + .build(); + + req3.imp[0].id = 'b4'; + + const serverReq1 = new ServerRequestBuilder() + .withData(req1) + .build(); + + const serverReq2 = new ServerRequestBuilder() + .withData(req2) + .withUrl('https://ssc.33across.com/api/v1/hb?guid=sample33xGUID123456780') + .build(); + + const serverReq3 = new ServerRequestBuilder() + .withData(req3) + .withUrl('https://ssc.33across.com/api/v1/hb?guid=sample33xGUID123456780') + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverReq1, serverReq2, serverReq3]); + }); + }); + + context('when SRA mode is not enabled', function() { + it('builds multiple requests, one corresponding to each Ad Unit', function() { + const bidRequests = new BidRequestsBuilder() + .addBid() + .addBid({ + bidId: 'b3', + adUnitCode: 'div-id', + params: { + siteId: 'sample33xGUID123456780', + productId: 'siab' + } + }) + .withBanner() + .withVideo({context: 'outstream'}) + .build(); + + const req1 = new TtxRequestBuilder() + .withProduct('siab') + .withBanner() + .withVideo() + .build(); + + const req2 = new TtxRequestBuilder() + .withProduct('siab') + .withBanner() + .withVideo() + .build(); + + req2.imp[0].id = 'b2'; + + const req3 = new TtxRequestBuilder('sample33xGUID123456780') + .withProduct('siab') + .withBanner() + .withVideo() + .build(); + + req3.imp[0].id = 'b3'; + + const serverReq1 = new ServerRequestBuilder() + .withData(req1) + .build(); + + const serverReq2 = new ServerRequestBuilder() + .withData(req2) + .build(); + + const serverReq3 = new ServerRequestBuilder() + .withData(req3) + .withUrl('https://ssc.33across.com/api/v1/hb?guid=sample33xGUID123456780') + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests) + .to.deep.equal([ + serverReq1, + serverReq2, + serverReq3 + ]); + }); + }); }); describe('interpretResponse', function() { let ttxRequest, serverRequest; + const videoBid = ''; beforeEach(function() { ttxRequest = new TtxRequestBuilder() @@ -1390,6 +1571,7 @@ describe('33acrossBidAdapter:', function () { page: 'https://test-url.com' }) .build(); + serverRequest = new ServerRequestBuilder() .withUrl('https://staging-ssc.33across.com/api/v1/hb') .withData(ttxRequest) @@ -1405,11 +1587,12 @@ describe('33acrossBidAdapter:', function () { const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [ { bid: [{ id: '1', + impid: 'b1', adm: '

I am an ad

', crid: 1, h: 250, @@ -1441,15 +1624,15 @@ describe('33acrossBidAdapter:', function () { }); it('interprets and returns the single video bid response', function() { - const videoBid = ''; const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [ { bid: [{ id: '1', + impid: 'b1', adm: videoBid, ext: { ttx: { @@ -1497,6 +1680,7 @@ describe('33acrossBidAdapter:', function () { { bid: [{ id: '1', + impid: 'b1', adm: '

I am an ad

', crid: 1, h: 250, @@ -1533,7 +1717,7 @@ describe('33acrossBidAdapter:', function () { const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [] }; @@ -1542,15 +1726,16 @@ describe('33acrossBidAdapter:', function () { }); context('when more than one bids are returned', function() { - it('interprets and returns the the first bid of the first seatbid', function() { + it('interprets and returns all bids', function() { const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [ { bid: [{ id: '1', + impid: 'b1', adm: '

I am an ad

', crid: 1, h: 250, @@ -1559,6 +1744,7 @@ describe('33acrossBidAdapter:', function () { }, { id: '2', + impid: 'b2', adm: '

I am an ad

', crid: 2, h: 250, @@ -1570,7 +1756,14 @@ describe('33acrossBidAdapter:', function () { { bid: [{ id: '3', - adm: '

I am an ad

', + impid: 'b3', + adm: videoBid, + ext: { + ttx: { + mediaType: 'video', + vastType: 'xml' + } + }, crid: 3, h: 250, w: 300, @@ -1579,21 +1772,50 @@ describe('33acrossBidAdapter:', function () { } ] }; - const bidResponse = { - requestId: 'b1', - bidderCode: BIDDER_CODE, - cpm: 0.0940, - width: 300, - height: 250, - ad: '

I am an ad

', - ttl: 60, - creativeId: 1, - mediaType: 'banner', - currency: 'USD', - netRevenue: true - }; + const bidResponse = [ + { + requestId: 'b1', + bidderCode: BIDDER_CODE, + cpm: 0.0940, + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 60, + creativeId: 1, + mediaType: 'banner', + currency: 'USD', + netRevenue: true + }, + { + requestId: 'b2', + bidderCode: BIDDER_CODE, + cpm: 0.0938, + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 60, + creativeId: 2, + mediaType: 'banner', + currency: 'USD', + netRevenue: true + }, + { + requestId: 'b3', + bidderCode: BIDDER_CODE, + cpm: 0.0938, + width: 300, + height: 250, + ad: videoBid, + vastXml: '', + ttl: 60, + creativeId: 3, + mediaType: 'video', + currency: 'USD', + netRevenue: true + } + ]; - expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); + expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal(bidResponse); }); }); }); diff --git a/test/spec/modules/7xbidBidAdapter_spec.js b/test/spec/modules/7xbidBidAdapter_spec.js deleted file mode 100644 index bed2c604349..00000000000 --- a/test/spec/modules/7xbidBidAdapter_spec.js +++ /dev/null @@ -1,160 +0,0 @@ -import {expect} from 'chai'; -import {spec, _getUrlVars} from 'modules/7xbidBidAdapter.js'; -import * as utils from 'src/utils.js'; - -const BASE_URI = '//bidder.7xbid.com/api/v1/prebid/banner' -const NATIVE_BASE_URI = '//bidder.7xbid.com/api/v1/prebid/native' - -describe('7xbid adapter', function() { - let bidRequests; - let nativeBidRequests; - - beforeEach(function() { - bidRequests = [ - { - bidder: '7xbid', - params: { - placementId: 1425292, - currency: 'USD' - } - } - ] - - nativeBidRequests = [ - { - bidder: '7xbid', - params: { - placementId: 1429695, - currency: 'USD' - }, - nativeParams: { - title: { - required: true, - len: 80 - }, - image: { - required: true, - sizes: [150, 50] - }, - sponsoredBy: { - required: true - } - } - } - ] - }) - describe('isBidRequestValid', function () { - it('valid bid case', function () { - let validBid = { - bidder: '7xbid', - params: { - placementId: 1425292, - currency: 'USD' - } - } - let isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); - }); - - it('invalid bid case: placementId is not passed', function() { - let validBid = { - bidder: '7xbid', - params: { - } - } - let isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }) - - it('invalid bid case: currency is not support', function() { - let validBid = { - bidder: '7xbid', - params: { - placementId: 1108295, - currency: 'AUD' - } - } - let isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }) - }) - - describe('buildRequests', function () { - it('sends bid request to ENDPOINT via GET', function () { - const request = spec.buildRequests(bidRequests)[0]; - expect(request.url).to.equal(BASE_URI); - expect(request.method).to.equal('GET'); - }); - - it('sends native bid request to ENDPOINT via GET', function () { - const request = spec.buildRequests(nativeBidRequests)[0]; - expect(request.url).to.equal(NATIVE_BASE_URI); - expect(request.method).to.equal('GET'); - }); - - it('buildRequests function should not modify original bidRequests object', function () { - let originalBidRequests = utils.deepClone(bidRequests); - let request = spec.buildRequests(bidRequests); - expect(bidRequests).to.deep.equal(originalBidRequests); - }); - - it('buildRequests function should not modify original nativeBidRequests object', function () { - let originalBidRequests = utils.deepClone(nativeBidRequests); - let request = spec.buildRequests(nativeBidRequests); - expect(nativeBidRequests).to.deep.equal(originalBidRequests); - }); - - it('Request params check', function() { - let request = spec.buildRequests(bidRequests)[0]; - const data = _getUrlVars(request.data) - expect(parseInt(data.placementid)).to.exist.and.to.equal(bidRequests[0].params.placementId); - expect(data.cur).to.exist.and.to.equal(bidRequests[0].params.currency); - }) - - it('Native request params check', function() { - let request = spec.buildRequests(nativeBidRequests)[0]; - const data = _getUrlVars(request.data) - expect(parseInt(data.placementid)).to.exist.and.to.equal(nativeBidRequests[0].params.placementId); - expect(data.cur).to.exist.and.to.equal(nativeBidRequests[0].params.currency); - }) - }) - - describe('interpretResponse', function () { - let response = { - 1425292: - { - 'creativeId': '', - 'cur': 'USD', - 'price': 0.0920, - 'width': 300, - 'height': 250, - 'requestid': '2e42361a6172bf', - 'adm': '' - } - } - - it('should get correct bid response', function () { - let expectedResponse = [ - { - 'requestId': '2e42361a6172bf', - 'cpm': 0.0920, - 'width': 300, - 'height': 250, - 'netRevenue': true, - 'currency': 'USD', - 'creativeId': '', - 'ttl': 700, - 'ad': '' - } - ]; - let request = spec.buildRequests(bidRequests)[0]; - let result = spec.interpretResponse({body: response}, request); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); - expect(result[0].cpm).to.not.equal(null); - expect(result[0].creativeId).to.not.equal(null); - expect(result[0].ad).to.not.equal(null); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - }); - }) -}) diff --git a/test/spec/modules/a4gBidAdapter_spec.js b/test/spec/modules/a4gBidAdapter_spec.js index cb05fa62ab6..a9ead9fd021 100644 --- a/test/spec/modules/a4gBidAdapter_spec.js +++ b/test/spec/modules/a4gBidAdapter_spec.js @@ -161,7 +161,11 @@ describe('a4gAdapterTests', function () { ad: 'test ad', currency: 'USD', ttl: 120, - netRevenue: true + netRevenue: true, + meta: { + advertiserDomains: [] + } + } ]; const result = spec.interpretResponse(bidResponse, bidRequest); @@ -181,7 +185,10 @@ describe('a4gAdapterTests', function () { ad: 'test ad', currency: 'USD', ttl: 120, - netRevenue: true + netRevenue: true, + meta: { + advertiserDomains: [] + } } ]; const result = spec.interpretResponse(bidResponseWithoutCrid, bidRequest); @@ -201,7 +208,8 @@ describe('a4gAdapterTests', function () { 'currency', 'netRevenue', 'ttl', - 'ad' + 'ad', + 'meta' ]; let resultKeys = Object.keys(result[0]); @@ -215,5 +223,13 @@ describe('a4gAdapterTests', function () { expect(result[0].requestId).to.not.equal(result[0].adId); }) + + it('advertiserDomains is included when sent by server', function () { + bidResponse.body[0].adomain = ['test_adomain']; + let response = spec.interpretResponse(bidResponse, bidRequest); + expect(Object.keys(response[0].meta)).to.include.members(['advertiserDomains']); + expect(response[0].meta.advertiserDomains).to.deep.equal(['test_adomain']); + delete bidResponse.body[0].adomain; + }); }); }); diff --git a/test/spec/modules/aardvarkBidAdapter_spec.js b/test/spec/modules/aardvarkBidAdapter_spec.js deleted file mode 100644 index 9671f961407..00000000000 --- a/test/spec/modules/aardvarkBidAdapter_spec.js +++ /dev/null @@ -1,569 +0,0 @@ -import { expect } from 'chai'; -import * as utils from 'src/utils.js'; -import { spec, resetUserSync } from 'modules/aardvarkBidAdapter.js'; - -describe('aardvarkAdapterTest', function () { - describe('forming valid bidRequests', function () { - it('should accept valid bidRequests', function () { - expect(spec.isBidRequestValid({ - bidder: 'aardvark', - params: { - ai: 'xiby', - sc: 'TdAx', - }, - sizes: [[300, 250]] - })).to.equal(true); - }); - - it('should reject invalid bidRequests', function () { - expect(spec.isBidRequestValid({ - bidder: 'aardvark', - params: { - ai: 'xiby', - }, - sizes: [[300, 250]] - })).to.equal(false); - }); - }); - - describe('executing network requests', function () { - const bidRequests = [{ - bidder: 'aardvark', - params: { - ai: 'xiby', - sc: 'TdAx', - }, - adUnitCode: 'aaa', - transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', - sizes: [300, 250], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337', - userId: { tdid: 'eff98622-b5fd-44fa-9a49-6e846922d532' } - }, - { - bidder: 'aardvark', - params: { - ai: 'xiby', - sc: 'RAZd', - host: 'adzone.pub.com' - }, - adUnitCode: 'bbb', - transactionId: '193995b4-7122-4739-959b-2463282a138b', - sizes: [[800, 600]], - bidId: '22aidtbx5eabd9', - bidderRequestId: '70deaff71c281d', - auctionId: 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' - }]; - - const bidderRequest = { - refererInfo: { - referer: 'https://example.com' - } - }; - - it('should use HTTP GET method', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - requests.forEach(function (requestItem) { - expect(requestItem.method).to.equal('GET'); - }); - }); - - it('should call the correct bidRequest url', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.match(new RegExp('^https:\/\/adzone.pub.com/xiby/TdAx_RAZd/aardvark\?')); - }); - - it('should have correct data', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].data.version).to.equal(1); - expect(requests[0].data.jsonp).to.equal(false); - expect(requests[0].data.TdAx).to.equal('1abgs362e0x48a8'); - expect(requests[0].data.rtkreferer).to.not.be.undefined; - expect(requests[0].data.RAZd).to.equal('22aidtbx5eabd9'); - }); - - it('should have tdid, it is available in bidRequest', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - requests.forEach(function (requestItem) { - expect(requestItem.data.tdid).to.equal('eff98622-b5fd-44fa-9a49-6e846922d532'); - }); - }); - }); - - describe('splitting multi-auction ad units into own requests', function () { - const bidRequests = [{ - bidder: 'aardvark', - params: { - ai: 'Toby', - sc: 'TdAx', - categories: ['cat1', 'cat2'] - }, - adUnitCode: 'aaa', - transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', - sizes: [300, 250], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337' - }, - { - bidder: 'aardvark', - params: { - ai: 'xiby', - sc: 'RAZd', - host: 'adzone.pub.com' - }, - adUnitCode: 'bbb', - transactionId: '193995b4-7122-4739-959b-2463282a138b', - sizes: [[800, 600]], - bidId: '22aidtbx5eabd9', - bidderRequestId: '70deaff71c281d', - auctionId: 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' - }]; - - const bidderRequest = { - refererInfo: { - referer: 'https://example.com' - } - }; - - it('should use HTTP GET method', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - requests.forEach(function (requestItem) { - expect(requestItem.method).to.equal('GET'); - }); - }); - - it('should call the correct bidRequest urls for each auction', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests[0].url).to.match(new RegExp('^https:\/\/bidder.rtk.io/Toby/TdAx/aardvark\?')); - expect(requests[0].data.categories.length).to.equal(2); - expect(requests[1].url).to.match(new RegExp('^https:\/\/adzone.pub.com/xiby/RAZd/aardvark\?')); - }); - - it('should have correct data', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(2); - expect(requests[0].data.version).to.equal(1); - expect(requests[0].data.jsonp).to.equal(false); - expect(requests[0].data.TdAx).to.equal('1abgs362e0x48a8'); - expect(requests[0].data.rtkreferer).to.not.be.undefined; - expect(requests[0].data.RAZd).to.be.undefined; - expect(requests[1].data.version).to.equal(1); - expect(requests[1].data.jsonp).to.equal(false); - expect(requests[1].data.TdAx).to.be.undefined; - expect(requests[1].data.rtkreferer).to.not.be.undefined; - expect(requests[1].data.RAZd).to.equal('22aidtbx5eabd9'); - }); - - it('should have no tdid, it is not available in bidRequest', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - requests.forEach(function (requestItem) { - expect(requestItem.data.tdid).to.be.undefined; - }); - }); - }); - - describe('GDPR conformity', function () { - const bidRequests = [{ - bidder: 'aardvark', - params: { - ai: 'xiby', - sc: 'TdAx', - }, - adUnitCode: 'aaa', - transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', - sizes: [300, 250], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337' - }]; - - const bidderRequest = { - gdprConsent: { - consentString: 'awefasdfwefasdfasd', - gdprApplies: true - }, - refererInfo: { - referer: 'https://example.com' - } - }; - - it('should transmit correct data', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].data.gdpr).to.equal(true); - expect(requests[0].data.consent).to.equal('awefasdfwefasdfasd'); - }); - }); - - describe('GDPR absence conformity', function () { - const bidRequests = [{ - bidder: 'aardvark', - params: { - ai: 'xiby', - sc: 'TdAx', - }, - adUnitCode: 'aaa', - transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', - sizes: [300, 250], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337' - }]; - - const bidderRequest = { - gdprConsent: undefined, - refererInfo: { - referer: 'https://example.com' - } - }; - - it('should transmit correct data', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].data.gdpr).to.be.undefined; - expect(requests[0].data.consent).to.be.undefined; - }); - }); - - describe('CCPA conformity', function () { - const bidRequests = [{ - bidder: 'aardvark', - params: { - ai: 'xiby', - sc: 'TdAx', - }, - adUnitCode: 'aaa', - transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', - sizes: [300, 250], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337' - }]; - - it('should transmit us_privacy data', function () { - const usp = '1NY-'; - const bidderRequest = { - gdprConsent: { - consentString: 'awefasdfwefasdfasd', - gdprApplies: true - }, - refererInfo: { - referer: 'http://example.com' - }, - uspConsent: usp - }; - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].data.gdpr).to.equal(true); - expect(requests[0].data.consent).to.equal('awefasdfwefasdfasd'); - expect(requests[0].data.us_privacy).to.equal(usp); - }); - - it('should not send us_privacy', function () { - const bidderRequest = { - refererInfo: { - referer: 'http://example.com' - } - }; - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].data.gdpr).to.be.undefined; - expect(requests[0].data.consent).to.be.undefined; - expect(requests[0].data.us_privacy).to.be.undefined; - }); - }); - - describe('interpretResponse', function () { - it('should handle bid responses', function () { - const serverResponse = { - body: [ - { - media: 'banner', - nurl: 'https://www.nurl.com/0', - cpm: 0.09, - width: 300, - height: 250, - cid: '22aidtbx5eabd9', - adm: '', - dealId: 'dealing', - ttl: 200, - }, - { - media: 'banner', - nurl: 'https://www.nurl.com/1', - cpm: 0.19, - width: 300, - height: 250, - cid: '1abgs362e0x48a8', - adm: '', - ttl: 200, - ex: 'extraproperty' - } - ], - headers: {} - }; - - const result = spec.interpretResponse(serverResponse, {}); - expect(result.length).to.equal(2); - - expect(result[0].requestId).to.equal('22aidtbx5eabd9'); - expect(result[0].cpm).to.equal(0.09); - expect(result[0].width).to.equal(300); - expect(result[0].height).to.equal(250); - expect(result[0].currency).to.equal('USD'); - expect(result[0].ttl).to.equal(200); - expect(result[0].dealId).to.equal('dealing'); - expect(result[0].ex).to.be.undefined; - expect(result[0].ad).to.not.be.undefined; - - expect(result[1].requestId).to.equal('1abgs362e0x48a8'); - expect(result[1].cpm).to.equal(0.19); - expect(result[1].width).to.equal(300); - expect(result[1].height).to.equal(250); - expect(result[1].currency).to.equal('USD'); - expect(result[1].ttl).to.equal(200); - expect(result[1].ad).to.not.be.undefined; - expect(result[1].ex).to.equal('extraproperty'); - }); - - it('should handle nobid responses', function () { - var emptyResponse = [{ - nurl: '', - cid: '9e5a09319e18f1', - media: 'banner', - error: 'No bids received for 9DgF', - adm: '', - id: '9DgF', - cpm: 0.00 - }]; - - var result = spec.interpretResponse({ body: emptyResponse }, {}); - expect(result.length).to.equal(0); - }); - }); - - describe('getUserSyncs', function () { - const syncOptions = { - iframeEnabled: true - }; - - it('should produce sync url', function () { - const syncs = spec.getUserSyncs(syncOptions); - expect(syncs.length).to.equal(1); - expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.equal('https://sync.rtk.io/cs'); - }); - - it('should return empty, as we sync only once', function () { - const syncs = spec.getUserSyncs(syncOptions); - expect(syncs.length).to.equal(0); - }); - - it('should reset hasSynced flag, allowing another sync', function () { - resetUserSync(); - - const syncs = spec.getUserSyncs(syncOptions); - expect(syncs.length).to.equal(1); - }); - - it('should return empty when iframe disallowed', function () { - resetUserSync(); - - const noIframeOptions = { iframeEnabled: false }; - const syncs = spec.getUserSyncs(noIframeOptions); - expect(syncs.length).to.equal(0); - }); - - it('should produce sync url with gdpr params', function () { - const gdprConsent = { - gdprApplies: true, - consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA' - }; - - resetUserSync(); - - const syncs = spec.getUserSyncs(syncOptions, null, gdprConsent); - expect(syncs.length).to.equal(1); - expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.equal('https://sync.rtk.io/cs?g=1&c=BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA'); - }); - - it('should produce sync url with ccpa params', function () { - resetUserSync(); - - const syncs = spec.getUserSyncs(syncOptions, null, {}, '1YYN'); - expect(syncs.length).to.equal(1); - expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.equal('https://sync.rtk.io/cs?us_privacy=1YYN'); - }); - }); - - describe('reading window.top properties', function () { - const bidCategories = ['bcat1', 'bcat2', 'bcat3']; - const bidRequests = [{ - bidder: 'aardvark', - params: { - ai: 'xiby', - sc: 'TdAx', - host: 'adzone.pub.com', - categories: bidCategories - }, - adUnitCode: 'RTK_aaaa', - transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', - sizes: [300, 250], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337', - userId: { tdid: 'eff98622-b5fd-44fa-9a49-6e846922d532' } - }]; - - const bidderRequest = { - refererInfo: { - referer: 'https://example.com' - } - }; - - const topWin = { - innerWidth: 1366, - innerHeight: 768, - rtkcategories: ['cat1', 'cat2', 'cat3'] - }; - - let sandbox; - beforeEach(function () { - sandbox = sinon.createSandbox(); - }); - - afterEach(function () { - sandbox.restore(); - }); - - it('should have window.top dimensions', function () { - sandbox.stub(utils, 'getWindowTop').returns(topWin); - - const requests = spec.buildRequests(bidRequests, bidderRequest); - requests.forEach(function (requestItem) { - expect(requestItem.data.w).to.equal(topWin.innerWidth); - expect(requestItem.data.h).to.equal(topWin.innerHeight); - }); - }); - - it('should have window dimensions, as backup', function () { - sandbox.stub(utils, 'getWindowTop').returns(undefined); - - const requests = spec.buildRequests(bidRequests, bidderRequest); - requests.forEach(function (requestItem) { - expect(requestItem.data.w).to.equal(window.innerWidth); - expect(requestItem.data.h).to.equal(window.innerHeight); - }); - }); - - it('should have window.top & bid categories', function () { - sandbox.stub(utils, 'getWindowTop').returns(topWin); - - const requests = spec.buildRequests(bidRequests, bidderRequest); - requests.forEach(function (requestItem) { - utils._each(topWin.categories, function (cat) { - expect(requestItem.data.categories).to.contain(cat); - }); - utils._each(bidCategories, function (cat) { - expect(requestItem.data.categories).to.contain(cat); - }); - }); - }); - }); - - describe('schain support', function() { - const nodePropsOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; - let schainConfig = { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'rtk.io', - sid: '1234', - hp: 1, - rid: 'bid-request-1', - name: 'first pub', - domain: 'first.com' - }, - { - asi: 'rtk.io', - sid: '5678', - hp: 1, - rid: 'bid-request-2', - name: 'second pub', - domain: 'second.com' - } - ] - }; - - const bidRequests = [{ - bidder: 'aardvark', - params: { - ai: 'xiby', - sc: 'TdAx', - }, - adUnitCode: 'aaa', - transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', - sizes: [300, 250], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337', - schain: schainConfig, - }]; - - const bidderRequest = { - gdprConsent: undefined, - refererInfo: { - referer: 'https://example.com' - } - }; - - it('should properly serialize schain object with correct delimiters', () => { - const results = spec.buildRequests(bidRequests, bidderRequest); - const numNodes = schainConfig.nodes.length; - - const schain = results[0].data.schain; - - // each node serialization should start with an ! - expect(schain.match(/!/g).length).to.equal(numNodes); - - // 5 commas per node plus 1 for version - expect(schain.match(/,/g).length).to.equal(numNodes * 5 + 1); - }); - - it('should send the proper version for the schain', () => { - const results = spec.buildRequests(bidRequests, bidderRequest); - const schain = decodeURIComponent(results[0].data.schain).split('!'); - const version = schain.shift().split(',')[0]; - expect(version).to.equal(bidRequests[0].schain.ver); - }); - - it('should send the correct value for complete in schain', () => { - const results = spec.buildRequests(bidRequests, bidderRequest); - const schain = decodeURIComponent(results[0].data.schain).split('!'); - const complete = schain.shift().split(',')[1]; - expect(complete).to.equal(String(bidRequests[0].schain.complete)); - }); - - it('should send available params in the right order', () => { - const results = spec.buildRequests(bidRequests, bidderRequest); - const schain = decodeURIComponent(results[0].data.schain).split('!'); - schain.shift(); - - schain.forEach((serializeNode, nodeIndex) => { - const nodeProps = serializeNode.split(','); - nodeProps.forEach((nodeProp, propIndex) => { - const node = schainConfig.nodes[nodeIndex]; - const key = nodePropsOrder[propIndex]; - expect(nodeProp).to.equal(node[key] ? String(node[key]) : ''); - }); - }); - }); - }); -}); diff --git a/test/spec/modules/adWMGBidAdapter_spec.js b/test/spec/modules/adWMGBidAdapter_spec.js index 8b927ace84c..db536ca14e2 100644 --- a/test/spec/modules/adWMGBidAdapter_spec.js +++ b/test/spec/modules/adWMGBidAdapter_spec.js @@ -209,7 +209,8 @@ describe('adWMGBidAdapter', function () { 'ttl': 300, 'creativeId': 'creative-id', 'netRevenue': true, - 'currency': 'USD' + 'currency': 'USD', + 'adomain': ['testdomain.com'] } }; }); @@ -219,7 +220,7 @@ describe('adWMGBidAdapter', function () { expect(responses).to.be.an('array').that.is.not.empty; let response = responses[0]; - expect(response).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + expect(response).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'meta', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency'); expect(response.requestId).to.equal('request-id'); expect(response.cpm).to.equal(100); @@ -230,6 +231,8 @@ describe('adWMGBidAdapter', function () { expect(response.creativeId).to.equal('creative-id'); expect(response.netRevenue).to.be.true; expect(response.currency).to.equal('USD'); + expect(response.meta.advertiserDomains[0]).to.equal('testdomain.com'); + expect(response.meta.mediaType).to.equal('banner'); }); it('should return an empty array when serverResponse is empty', () => { diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 3707c19e471..945cd0565a7 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,20 +1,23 @@ -import find from 'core-js-pure/features/array/find.js'; -import { expect, util } from 'chai'; +import { expect } from 'chai'; import { _features, internal as adagio, adagioScriptFromLocalStorageCb, getAdagioScript, storage, + setExtraParam, spec, ENDPOINT, VERSION, - RENDERER_URL + RENDERER_URL, + GlobalExchange } from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; import * as utils from '../../../src/utils.js'; import { config } from '../../../src/config.js'; import { NATIVE } from '../../../src/mediaTypes.js'; +import * as prebidGlobal from 'src/prebidGlobal.js'; +import { executeRenderer } from '../../../src/Renderer.js'; const BidRequestBuilder = function BidRequestBuilder(options) { const defaults = { @@ -115,6 +118,9 @@ describe('Adagio bid adapter', () => { window.ADAGIO.versions.adagioBidderAdapter = VERSION; window.ADAGIO.pageviewId = 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a'; + GlobalExchange.clearFeatures(); + GlobalExchange.clearExchangeData(); + adagioMock = sinon.mock(adagio); utilsMock = sinon.mock(utils); @@ -130,6 +136,46 @@ describe('Adagio bid adapter', () => { sandbox.restore(); }); + describe('get and set params at adUnit level from global Prebid configuration', function() { + it('should set params get from ortb2 config or bidderSettings. Priority to bidderSetting', function() { + const bid = new BidRequestBuilder().build(); + + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + adagio: { + pagetype: 'article' + }, + ortb2: { + site: { + ext: { + data: { + environment: 'desktop', + pagetype: 'abc' + } + } + } + } + }; + return utils.deepAccess(config, key); + }); + + setExtraParam(bid, 'environment'); + expect(bid.params.environment).to.equal('desktop'); + + setExtraParam(bid, 'pagetype') + expect(bid.params.pagetype).to.equal('article'); + }); + + it('should use the adUnit param unit if defined', function() { + const bid = new BidRequestBuilder({ params: { pagetype: 'article' } }).build(); + sandbox.stub(config, 'getConfig').withArgs('adagio').returns({ + pagetype: 'ignore-me' + }); + setExtraParam(bid, 'pagetype') + expect(bid.params.pagetype).to.equal('article'); + }); + }) + describe('isBidRequestValid()', function() { it('should return true when required params have been found', function() { const bid = new BidRequestBuilder().withParams().build(); @@ -137,6 +183,19 @@ describe('Adagio bid adapter', () => { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + it('should compute organizationId and site params from global BidderSettings config', function() { + sandbox.stub(adagio, 'getRefererInfo').returns({ reachedTop: true }); + sandbox.stub(config, 'getConfig').withArgs('adagio').returns({ + siteId: '1000:SITE-NAME' + }); + + const bid = new BidRequestBuilder({ + params: { placement: 'PAVE_ATF' } + }).build(); + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }) + it('should return false if bid.params is missing', function() { sandbox.spy(utils, 'logWarn'); const bid01 = new BidRequestBuilder().build(); @@ -156,7 +215,7 @@ describe('Adagio bid adapter', () => { expect(spec.isBidRequestValid(bid01)).to.equal(true); expect(bid01.params.adUnitElementId).to.equal('adunit-code'); expect(bid01.params.placement).to.equal('adunit-code'); - }) + }); it('should return false when a required param is missing', function() { const bid01 = new BidRequestBuilder({ params: { @@ -174,147 +233,16 @@ describe('Adagio bid adapter', () => { site: 'SITE-NAME' }}).build(); - expect(spec.isBidRequestValid(bid01)).to.equal(false); - expect(spec.isBidRequestValid(bid02)).to.equal(false); - expect(spec.isBidRequestValid(bid03)).to.equal(false); - }); - - it('should return false when refererInfo.reachedTop is false', function() { - sandbox.spy(utils, 'logWarn'); - sandbox.stub(adagio, 'getRefererInfo').returns({ reachedTop: false }); - const bid = new BidRequestBuilder().withParams().build(); - - expect(spec.isBidRequestValid(bid)).to.equal(false); - sinon.assert.callCount(utils.logWarn, 1); - sinon.assert.calledWith(utils.logWarn, 'Adagio: the main page url is unreachabled.'); - }); - - it('should log warning and enqueue the bid object in ADAGIO.queue when isBidRequestValid is false', function() { - sandbox.stub(Date, 'now').returns(12345); - sandbox.spy(utils, 'logWarn'); - sandbox.spy(adagio, 'enqueue'); - - const bid = new BidRequestBuilder({'params': { - organizationId: '1000', - placement: 'PAVE_ATF' - }}).build(); - - const expectedEnqueued = { - action: 'pb-dbg', - ts: 12345, - data: { bid } - }; - - spec.isBidRequestValid(bid); - - sinon.assert.calledWith(adagio.enqueue, expectedEnqueued); - sinon.assert.callCount(utils.logWarn, 1); - }); - - describe('Store ADAGIO global in window.top or window.self depending on context', function() { - const bid01 = new BidRequestBuilder({ - adUnitCode: 'adunit-code-01', - sizes: [[300, 250], [300, 600]] - }).withParams().build(); - - const bid02 = new BidRequestBuilder({ - adUnitCode: 'adunit-code-02', - mediaTypes: { - banner: { sizes: [[300, 250]] } - }, - }).withParams().build(); - - const bid03 = new BidRequestBuilder({ - adUnitCode: 'adunit-code-02', - mediaTypes: { - banner: { sizes: [[300, 600]] } - }, - }).withParams().build(); - - const expected = [ - { - code: 'adunit-code-01', - sizes: [[300, 250], [300, 600]], - mediaTypes: {}, - bids: [{ - bidder: 'adagio', - params: { - organizationId: '1000', - placement: 'PAVE_ATF', - site: 'SITE-NAME', - adUnitElementId: 'gpt-adunit-code', - environment: 'desktop', - supportIObs: true - } - }], - auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', - pageviewId: 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a', - printNumber: 1, - }, - { - code: 'adunit-code-02', - sizes: [[300, 600]], - mediaTypes: { - banner: { sizes: [[300, 600]] } - }, - bids: [{ - bidder: 'adagio', - params: { - organizationId: '1000', - placement: 'PAVE_ATF', - site: 'SITE-NAME', - adUnitElementId: 'gpt-adunit-code', - environment: 'desktop', - supportIObs: true - } - }], - auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', - pageviewId: 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a', - printNumber: 2, - } - ]; - - it('should store bids config once by bid in window.top if it accessible', function() { - sandbox.stub(adagio, 'getCurrentWindow').returns(window.top); - sandbox.stub(adagio, 'supportIObs').returns(true); - - // replace by the values defined in beforeEach - window.top.ADAGIO = { - ...window.ADAGIO - }; - - spec.isBidRequestValid(bid01); - spec.isBidRequestValid(bid02); - spec.isBidRequestValid(bid03); - - expect(find(window.top.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-01')).to.deep.eql(expected[0]); - expect(find(window.top.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-02')).to.deep.eql(expected[1]); + sandbox.stub(config, 'getConfig').withArgs('adagio').returns({ + siteId: '1000' }); - it('should detect IntersectionObserver support', function() { - sandbox.stub(adagio, 'getCurrentWindow').returns(window.top); - sandbox.stub(adagio, 'supportIObs').returns(false); + const bid04 = new BidRequestBuilder({ params: { placement: 'PAVE_ATF' } }).build(); - window.top.ADAGIO = { - ...window.ADAGIO - }; - - spec.isBidRequestValid(bid01); - const validBidReq = find(window.top.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-01'); - expect(validBidReq.bids[0].params.supportIObs).to.equal(false); - }); - - it('should store bids config once by bid in current window', function() { - sandbox.stub(adagio, 'getCurrentWindow').returns(window.self); - sandbox.stub(adagio, 'supportIObs').returns(true); - - spec.isBidRequestValid(bid01); - spec.isBidRequestValid(bid02); - spec.isBidRequestValid(bid03); - - expect(find(window.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-01')).to.deep.eql(expected[0]); - expect(find(window.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-02')).to.deep.eql(expected[1]); - }); + expect(spec.isBidRequestValid(bid01)).to.equal(false); + expect(spec.isBidRequestValid(bid02)).to.equal(false); + expect(spec.isBidRequestValid(bid03)).to.equal(false); + expect(spec.isBidRequestValid(bid04)).to.equal(false); }); }); @@ -331,7 +259,6 @@ describe('Adagio bid adapter', () => { 'user', 'schain', 'prebidVersion', - 'adapterVersion', 'featuresVersion', 'data' ]; @@ -360,7 +287,6 @@ describe('Adagio bid adapter', () => { sandbox.stub(adagio, 'getDevice').returns({ a: 'a' }); sandbox.stub(adagio, 'getSite').returns({ domain: 'adagio.io', 'page': 'https://adagio.io/hb' }); sandbox.stub(adagio, 'getPageviewId').returns('1234-567'); - sandbox.stub(adagio, 'getFeatures').returns({}); const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); @@ -377,24 +303,11 @@ describe('Adagio bid adapter', () => { it('should enqueue computed features for collect usage', function() { sandbox.stub(Date, 'now').returns(12345); - for (const prop in _features) { - sandbox.stub(_features, prop).returns(''); - } - - adagioMock.expects('enqueue').withExactArgs({ - action: 'features', - ts: 12345, - data: { - 'gpt-adunit-code': { - features: {}, - version: '1' - } - } - }).atLeast(1); - const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); + adagioMock.expects('enqueue').withArgs(sinon.match({ action: 'features' })).atLeast(1); + const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data).to.have.all.keys(expectedDataKeys); @@ -497,7 +410,7 @@ describe('Adagio bid adapter', () => { const requests = spec.buildRequests([bid01], bidderRequest); expect(requests).to.have.lengthOf(1); expect(requests[0].data.adUnits[0].mediaTypes.video).to.deep.equal(expected); - sinon.assert.calledTwice(utils.logWarn); + sinon.assert.calledTwice(utils.logWarn.withArgs(sinon.match(new RegExp(/^Adagio: The OpenRTB/)))); }); }); @@ -579,6 +492,7 @@ describe('Adagio bid adapter', () => { const expected = { consentString, + allowAuctionWithoutConsent: 0, consentRequired: 1, apiVersion: 2 }; @@ -615,6 +529,7 @@ describe('Adagio bid adapter', () => { const expected = { consentString, consentRequired: 0, + allowAuctionWithoutConsent: 0, apiVersion: 2 }; @@ -694,7 +609,7 @@ describe('Adagio bid adapter', () => { describe('with userID modules', function() { const userId = { - sharedid: {id: '01EAJWWNEPN3CYMM5N8M5VXY22', third: '01EAJWWNEPN3CYMM5N8M5VXY22'}, + pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', unsuported: '666' }; @@ -708,13 +623,10 @@ describe('Adagio bid adapter', () => { const requests = spec.buildRequests([bid01], bidderRequest); const expected = [{ - source: 'sharedid.org', + source: 'pubcid.org', uids: [ { atype: 1, - ext: { - third: '01EAJWWNEPN3CYMM5N8M5VXY22' - }, id: '01EAJWWNEPN3CYMM5N8M5VXY22' } ] @@ -738,6 +650,77 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.user.eids).to.be.empty; }); }); + + describe('with priceFloors module', function() { + it('should get and set floor by mediatype and sizes', function() { + const bid01 = new BidRequestBuilder({ + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + playerSize: [600, 480] + } + } + }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + + // delete the computed `sizes` prop as we are based on mediaTypes only. + delete bid01.sizes + + bid01.getFloor = () => { + return { floor: 1, currency: 'USD' } + } + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.adUnits[0].floors.length).to.equal(3); + expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({f: 1, mt: 'banner', s: '300x250'}); + expect(requests[0].data.adUnits[0].floors[1]).to.deep.equal({f: 1, mt: 'banner', s: '300x600'}); + expect(requests[0].data.adUnits[0].floors[2]).to.deep.equal({f: 1, mt: 'video', s: '600x480'}); + }); + + it('should get and set floor by mediatype if no size provided (ex native, video)', function() { + const bid01 = new BidRequestBuilder({ + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + }, + native: { + body: { required: true } + } + } + }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + bid01.getFloor = () => { + return { floor: 1, currency: 'USD' } + } + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.adUnits[0].floors.length).to.equal(2); + expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({f: 1, mt: 'video'}); + expect(requests[0].data.adUnits[0].floors[1]).to.deep.equal({f: 1, mt: 'native'}); + }); + + it('should get and set floor with default value if no floors found', function() { + const bid01 = new BidRequestBuilder({ + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + } + } + }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + bid01.getFloor = () => { + return { floor: NaN, currency: 'USD' } + } + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.adUnits[0].floors.length).to.equal(1); + expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({f: 0.1, mt: 'video'}); + }); + }); }); describe('interpretResponse()', function() { @@ -755,7 +738,14 @@ describe('Adagio bid adapter', () => { netRevenue: true, requestId: 'c180kg4267tyqz', ttl: 360, - width: 300 + width: 300, + aDomain: ['advertiser.com'], + mediaType: 'banner', + meta: { + advertiserId: '80', + advertiserName: 'An Advertiser', + networkId: '110' + } }] } }; @@ -821,13 +811,53 @@ describe('Adagio bid adapter', () => { pagetype: 'ARTICLE', category: 'NEWS', subcategory: 'SPORT', - environment: 'desktop' + environment: 'desktop', + aDomain: ['advertiser.com'], + mediaType: 'banner', + meta: { + advertiserId: '80', + advertiserName: 'An Advertiser', + advertiserDomains: ['advertiser.com'], + networkId: '110', + mediaType: 'banner' + } }]; expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array'); expect(spec.interpretResponse(serverResponse, bidRequest)).to.deep.equal(expectedResponse); }); + it('Meta props should be limited if no bid.meta is provided', function() { + const altServerResponse = utils.deepClone(serverResponse); + delete altServerResponse.body.bids[0].meta; + + let expectedResponse = [{ + ad: '
', + cpm: 1, + creativeId: 'creativeId', + currency: 'EUR', + height: 250, + netRevenue: true, + requestId: 'c180kg4267tyqz', + ttl: 360, + width: 300, + placement: 'PAVE_ATF', + site: 'SITE-NAME', + pagetype: 'ARTICLE', + category: 'NEWS', + subcategory: 'SPORT', + environment: 'desktop', + aDomain: ['advertiser.com'], + mediaType: 'banner', + meta: { + advertiserDomains: ['advertiser.com'], + mediaType: 'banner' + } + }]; + + expect(spec.interpretResponse(altServerResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + it('should populate ADAGIO queue with ssp-data', function() { sandbox.stub(Date, 'now').returns(12345); @@ -880,6 +910,20 @@ describe('Adagio bid adapter', () => { expect(bidResponse.height).to.equal(250); expect(bidResponse.vastUrl).to.match(/^data:text\/xml;/) }); + + it('should execute Adagio outstreamPlayer if defined', function() { + window.ADAGIO.outstreamPlayer = sinon.stub(); + const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; + executeRenderer(bidResponse.renderer, bidResponse) + sinon.assert.calledOnce(window.ADAGIO.outstreamPlayer); + delete window.ADAGIO.outstreamPlayer; + }); + + it('should logError if Adagio outstreamPlayer is not defined', function() { + const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; + executeRenderer(bidResponse.renderer, bidResponse) + utilsMock.expects('logError').withExactArgs('Adagio: Adagio outstream player is not defined').once(); + }); }); describe('Response with native add', () => { @@ -889,7 +933,7 @@ describe('Adagio bid adapter', () => { ver: '1.2', link: { url: 'https://i.am.a.click.url', - clickTrackers: [ + clicktrackers: [ 'https://i.am.a.clicktracker.url' ] }, @@ -916,7 +960,6 @@ describe('Adagio bid adapter', () => { ], assets: [ { - required: 1, title: { text: 'My title' } @@ -964,34 +1007,32 @@ describe('Adagio bid adapter', () => { }; const bidRequestNative = utils.deepClone(bidRequest) - bidRequestNative.mediaTypes = { - native: { - sendTargetingKeys: false, + bidRequestNative.nativeParams = { + sendTargetingKeys: false, - clickUrl: { - required: true, - }, - title: { - required: true, - }, - body: { - required: true, - }, - sponsoredBy: { - required: false - }, - image: { - required: true - }, - icon: { - required: true - }, - privacyLink: { - required: false - }, - ext: { - adagio_bvw: {} - } + clickUrl: { + required: true, + }, + title: { + required: true, + }, + body: { + required: true, + }, + sponsoredBy: { + required: false + }, + image: { + required: true + }, + icon: { + required: true + }, + privacyLink: { + required: false + }, + ext: { + adagio_bvw: {} } }; @@ -1082,12 +1123,62 @@ describe('Adagio bid adapter', () => { }); }); - describe('Adagio features', function() { + describe('transformBidParams', function() { + it('Compute additional params in s2s mode', function() { + GlobalExchange.prepareExchangeData('{}'); + + sandbox.stub(window.top.document, 'getElementById').returns( + fixtures.getElementById() + ); + sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'block' }); + sandbox.stub(utils, 'inIframe').returns(false); + + const adUnit = { + code: 'adunit-code', + params: { + organizationId: '1000' + } + }; + const bid01 = new BidRequestBuilder({ + 'mediaTypes': { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [300, 250], + renderer: { + url: 'https://url.tld', + render: () => true + } + } + } + }).withParams().build(); + + const params = spec.transformBidParams({ organizationId: '1000' }, true, adUnit, [{ bidderCode: 'adagio', auctionId: bid01.auctionId, bids: [bid01] }]); + + expect(params.organizationId).to.exist; + expect(params.auctionId).to.exist; + expect(params.playerName).to.exist; + expect(params.playerName).to.equal('other'); + expect(params.features).to.exist; + expect(params.features.page_dimensions).to.exist; + expect(params.features.adunit_position).to.exist; + expect(params.features.dom_loading).to.exist; + expect(params.features.print_number).to.exist; + expect(params.features.user_timestamp).to.exist; + expect(params.placement).to.exist; + expect(params.adUnitElementId).to.exist; + expect(params.site).to.exist; + expect(params.data.session).to.exist; + }); + }); + + describe('Adagio features when prebid in top.window', function() { it('should return all expected features when all expected bidder params are available', function() { sandbox.stub(window.top.document, 'getElementById').returns( fixtures.getElementById() ); sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'block' }); + sandbox.stub(utils, 'inIframe').returns(false); const bidRequest = new BidRequestBuilder({ 'mediaTypes': { @@ -1097,7 +1188,8 @@ describe('Adagio bid adapter', () => { const bidderRequest = new BidderRequestBuilder().build(); - const result = adagio.getFeatures(bidRequest, bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const result = requests[0].data.adUnits[0].features; expect(result.adunit_position).to.match(/^[\d]+x[\d]+$/); expect(result.page_dimensions).to.match(/^[\d]+x[\d]+$/); @@ -1105,13 +1197,15 @@ describe('Adagio bid adapter', () => { expect(result.print_number).to.be.a('String'); expect(result.dom_loading).to.be.a('String'); expect(result.user_timestamp).to.be.a('String'); - expect(result.url).to.be.a('String'); - expect(result.device).to.be.a('String'); - expect(result.os).to.be.a('String'); - expect(result.browser).to.be.a('String'); + expect(result.url).to.not.exist; + expect(result.device).to.not.exist; + expect(result.os).to.not.exist; + expect(result.browser).to.not.exist; }); it('should return all expected features when `adUnitElementId` param is not available', function() { + sandbox.stub(utils, 'inIframe').returns(false); + const bidRequest = new BidRequestBuilder({ params: { organizationId: '1000', @@ -1125,7 +1219,8 @@ describe('Adagio bid adapter', () => { const bidderRequest = new BidderRequestBuilder().build(); - const result = adagio.getFeatures(bidRequest, bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const result = requests[0].data.adUnits[0].features; expect(result.adunit_position).to.not.exist; expect(result.page_dimensions).to.be.a('String'); @@ -1133,16 +1228,23 @@ describe('Adagio bid adapter', () => { expect(result.print_number).to.be.a('String'); expect(result.dom_loading).to.be.a('String'); expect(result.user_timestamp).to.be.a('String'); - expect(result.url).to.be.a('String'); - expect(result.device).to.be.a('String'); - expect(result.os).to.be.a('String'); - expect(result.browser).to.be.a('String'); }); + }); - it('should not return feature with an empty value', function() { - sandbox.stub(_features, 'getDomLoadingDuration').returns(''); - sandbox.stub(_features, 'getUrl').returns(''); - sandbox.stub(_features, 'getBrowser').returns(''); + describe('Adagio features when prebid in Safeframe', function() { + beforeEach(function () { + window.$sf = $sf; + }); + + afterEach(function () { + delete window.$sf; + }); + + it('should return all expected features when prebid is in safeframe iframe', function() { + sandbox.stub(window.$sf.ext, 'geom').returns({ + win: {t: 23, r: 1920, b: 1200, l: 0, w: 1920, h: 1177}, + self: {t: 210, r: 1159, b: 460, l: 859, w: 300, h: 250}, + }); const bidRequest = new BidRequestBuilder({ 'mediaTypes': { @@ -1152,178 +1254,85 @@ describe('Adagio bid adapter', () => { const bidderRequest = new BidderRequestBuilder().build(); - const result = adagio.getFeatures(bidRequest, bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const result = requests[0].data.adUnits[0].features; - expect(result.adunit_position).to.not.exist; - expect(result.page_dimensions).to.exist; - expect(result.viewport_dimensions).to.exist; - expect(result.print_number).to.exist; - expect(result.dom_loading).to.not.exist; - expect(result.user_timestamp).to.exist; - expect(result.url).to.not.exist; - expect(result.device).to.exist; - expect(result.os).to.exist; - expect(result.browser).to.not.exist; + expect(result.page_dimensions).to.not.exist; + expect(result.viewport_dimensions).to.be.a('String'); + expect(result.print_number).to.be.a('String'); + expect(result.dom_loading).to.be.a('String'); + expect(result.user_timestamp).to.be.a('String'); + expect(result.adunit_position).to.exist; }); - describe('getPageDimensions feature', function() { - afterEach(() => { - delete window.$sf; - }); - - it('should not compute the page dimensions in cross-origin iframe', function() { - sandbox.stub(utils, 'getWindowTop').throws(); - const result = _features.getPageDimensions(); - expect(result).to.eq(''); - }); + it('should return all expected features when prebid safeframe api not properly implemented', function() { + const bidRequest = new BidRequestBuilder({ + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).withParams().build(); - it('should not compute the page dimensions even with safeFrame api', function() { - window.$sf = $sf; - const result = _features.getPageDimensions(); - expect(result).to.eq(''); - }); + const bidderRequest = new BidderRequestBuilder().build(); - it('should not compute the page dimensions if is not in the DOM', function() { - sandbox.stub(window.top.document, 'querySelector').withArgs('body').returns(null); - const result = _features.getPageDimensions(); - expect(result).to.eq(''); - }); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const result = requests[0].data.adUnits[0].features; - it('should compute the page dimensions based on body and viewport dimensions', function() { - sandbox.stub(window.top.document, 'querySelector').withArgs('body').returns({ scrollWidth: 1360, offsetWidth: 1280, scrollHeight: 2000, offsetHeight: 1000 }); - const result = _features.getPageDimensions(); - expect(result).to.eq('1360x2000'); - }); + expect(result.page_dimensions).to.not.exist; + expect(result.viewport_dimensions).to.not.exist; + expect(result.print_number).to.be.a('String'); + expect(result.dom_loading).to.be.a('String'); + expect(result.user_timestamp).to.be.a('String'); + expect(result.adunit_position).to.not.exist; }); - describe('getViewPortDimensions feature', function() { - afterEach(() => { - delete window.$sf; - }); - - it('should not compute the viewport dimensions in cross-origin iframe', function() { - sandbox.stub(utils, 'getWindowTop').throws(); - const result = _features.getViewPortDimensions(); - expect(result).to.eq(''); - }); + it('should return all expected features when prebid safeframe api not properly implemented bis', function() { + window.$sf.ext.geom = undefined; - it('should compute the viewport dimensions in cross-origin iframe w/ safeFrame api', function() { - window.$sf = $sf; - sandbox.stub(window.$sf.ext, 'geom').returns({ - win: {t: 23, r: 1920, b: 1200, l: 0, w: 1920, h: 1177}, - self: {t: 210, r: 1159, b: 460, l: 859, w: 300, h: 250}, - }); - const result = _features.getViewPortDimensions(); - expect(result).to.eq('1920x1177'); - }); - - it('should not compute the viewport dimensions if safeFrame api is misimplemented', function() { - window.$sf = { - ext: { geom: 'nothing' } - }; - const result = _features.getViewPortDimensions(); - expect(result).to.eq(''); - }); + const bidRequest = new BidRequestBuilder({ + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).withParams().build(); - it('should not compute the viewport dimensions if is not in the DOM', function() { - const querySelectorSpy = sandbox.spy(() => null); - sandbox.stub(utils, 'getWindowTop').returns({ - location: { href: 'https://mytest.io' }, - document: { querySelector: querySelectorSpy } - }); - const result = _features.getViewPortDimensions(); - expect(result).to.eq(''); - }); + const bidderRequest = new BidderRequestBuilder().build(); - it('should compute the viewport dimensions based on window', function() { - sandbox.stub(utils, 'getWindowTop').returns({ - location: { href: 'https://mytest.io' }, - innerWidth: 960, - innerHeight: 3000 - }); - const result = _features.getViewPortDimensions(); - expect(result).to.eq('960x3000'); - }); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const result = requests[0].data.adUnits[0].features; - it('should compute the viewport dimensions based on body', function() { - const querySelectorSpy = sandbox.spy(() => ({ clientWidth: 1024, clientHeight: 2000 })); - sandbox.stub(utils, 'getWindowTop').returns({ - location: { href: 'https://mytest.io' }, - document: { querySelector: querySelectorSpy } - }); - const result = _features.getViewPortDimensions(); - expect(result).to.eq('1024x2000'); - }); + expect(result.page_dimensions).to.not.exist; + expect(result.viewport_dimensions).to.not.exist; + expect(result.print_number).to.be.a('String'); + expect(result.dom_loading).to.be.a('String'); + expect(result.user_timestamp).to.be.a('String'); + expect(result.adunit_position).to.not.exist; }); + }); - describe('getSlotPosition feature', function() { - let getElementByIdStub; - let getComputedStyleStub; - - beforeEach(() => { - getElementByIdStub = sandbox.stub(window.top.document, 'getElementById'); - getElementByIdStub.returns(fixtures.getElementById()); - getComputedStyleStub = sandbox.stub(window.top, 'getComputedStyle'); - getComputedStyleStub.returns({ display: 'block' }); - }); - - afterEach(() => { - delete window.$sf; - getElementByIdStub.restore(); - getComputedStyleStub.restore(); - }); - - it('should not compute the slot position in cross-origin iframe', function() { - sandbox.stub(utils, 'getWindowTop').throws(); - const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); - expect(result).to.eq(''); - }); - - it('should compute the slot position in cross-origin iframe w/ safeFrame api', function() { - window.$sf = $sf; - sandbox.stub(window.$sf.ext, 'geom').returns({ - win: {t: 23, r: 1920, b: 1200, l: 0, w: 1920, h: 1177}, - self: {t: 210, r: 1159, b: 460, l: 859, w: 300, h: 250}, - }); - const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); - expect(result).to.eq('210x859'); - }); - - it('should not compute the slot position if safeFrame api is misimplemented', function() { - window.$sf = { - ext: { geom: 'nothing' } - }; - utilsMock.expects('logWarn').once(); - const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); - expect(result).to.eq(''); - utilsMock.verify(); - }); + describe('Adagio features when prebid in crossdomain iframe', function() { + it('should return all expected features', function() { + sandbox.stub(utils, 'getWindowTop').throws(); - it('should not compute the slot position due to unreachable adUnitElementId', function() { - getElementByIdStub.returns(null); - const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); - expect(result).to.eq(''); - }); + const bidRequest = new BidRequestBuilder({ + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).withParams().build(); - it('should use a quick switch to display slot and compute position', function() { - getComputedStyleStub.returns({ display: 'none' }); - const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); - expect(result).to.eq('800x300'); - }); + const bidderRequest = new BidderRequestBuilder().build(); - it('should compute the slot position based on window.top w/o postBid param', function() { - const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); - expect(result).to.eq('800x300'); - }); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const result = requests[0].data.adUnits[0].features; - it.skip('should compute the slot position inside the parent window (window.top) when safeFrame is not available and postBid params is `true`', function() { - const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: true }); - // expect(result).to.eq('800x300'); - }); + expect(result.page_dimensions).to.not.exist; + expect(result.viewport_dimensions).to.not.exist; + expect(result.print_number).to.be.a('String'); + expect(result.dom_loading).to.be.a('String'); + expect(result.user_timestamp).to.be.a('String'); + expect(result.adunit_position).to.not.exist; }); }); - describe('optional params auto detection', function() { + describe.skip('optional params auto detection', function() { it('should auto detect environment', function() { const getDeviceStub = sandbox.stub(_features, 'getDevice'); @@ -1343,7 +1352,7 @@ describe('Adagio bid adapter', () => { }); }); - describe('print number handling', function() { + describe.skip('print number handling', function() { it('should return 1 if no adunit-code found. This means it is the first auction', function() { sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); expect(adagio.computePrintNumber('adunit-code')).to.eql(1); diff --git a/test/spec/modules/adbookpspBidAdapter_spec.js b/test/spec/modules/adbookpspBidAdapter_spec.js new file mode 100755 index 00000000000..3a49f25edb6 --- /dev/null +++ b/test/spec/modules/adbookpspBidAdapter_spec.js @@ -0,0 +1,1337 @@ +import { expect } from 'chai'; +import * as utils from '../../../src/utils.js'; +import { + spec, + storage, + DEFAULT_BIDDER_CONFIG, + VERSION, + common, +} from '../../../modules/adbookpspBidAdapter.js'; + +describe('adbookpsp bid adapter', () => { + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + + sandbox + .stub(common, 'generateUUID') + .returns('54444444-5444-4444-9444-544444444444'); + sandbox.stub(common, 'getWindowDimensions').returns({ + innerWidth: 100, + innerHeight: 100, + }); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('isBidRequestValid()', () => { + it('should return false when there is no banner in mediaTypes', () => { + const bid = utils.deepClone(bannerBid); + delete bid.mediaTypes.banner; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when orgId and placementId is not defined', () => { + const bid = utils.deepClone(bannerBid); + delete bid.params.placementId; + delete bid.params.orgId; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return true when orgId is set in config', () => { + const bid = utils.deepClone(bannerBid); + + delete bid.params.placementId; + delete bid.params.orgId; + + sandbox + .stub(common, 'getConfig') + .withArgs('adbookpsp.orgId') + .returns('129576'); + + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bannerBid)).to.equal(true); + expect(spec.isBidRequestValid(videoBid)).to.equal(true); + expect(spec.isBidRequestValid(mixedBid)).to.equal(true); + }); + + it('should return false when sizes for banner are not specified', () => { + const bid = utils.deepClone(bannerBid); + delete bid.mediaTypes.banner.sizes; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when sizes for banner are invalid', () => { + const bid = utils.deepClone(bannerBid); + delete bid.mediaTypes.banner.sizes; + + bid.mediaTypes.banner.sizes = [['123', 'foo']]; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true if player size is set via playerSize', () => { + expect(spec.isBidRequestValid(videoBid)).to.equal(true); + }); + + it('should return true if player size is set via w and h', () => { + const bid = utils.deepClone(videoBid); + delete bid.mediaTypes.video.playerSize; + + bid.mediaTypes.video.w = 400; + bid.mediaTypes.video.h = 300; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should reutrn false if player size is not set', () => { + const bid = utils.deepClone(videoBid); + delete bid.mediaTypes.video.playerSize; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests()', () => { + it('should build correct request for banner bid', () => { + sandbox + .stub(common, 'getConfig') + .withArgs('adbookpsp.orgId') + .returns(undefined) + .withArgs('adbookpsp.exchangeUrl') + .returns('https://ex.fattail.com/openrtb2'); + + const requests = spec.buildRequests([bannerBid], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0]).to.deep.include({ + method: 'POST', + url: 'https://ex.fattail.com/openrtb2', + options: { + contentType: 'application/json', + withCredentials: true, + }, + }); + expect(JSON.parse(requests[0].data)).to.deep.equal(bannerExchangeRequest); + }); + + it('should build correct request for video bid', () => { + sandbox + .stub(common, 'getConfig') + .withArgs('adbookpsp') + .returns(DEFAULT_BIDDER_CONFIG) + .withArgs('adbookpsp.exchangeUrl') + .returns(DEFAULT_BIDDER_CONFIG.exchangeUrl) + .withArgs('adbookpsp.orgId') + .returns(undefined); + + const requests = spec.buildRequests([videoBid], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0]).to.deep.include({ + method: 'POST', + url: 'https://ex.fattail.com/openrtb2', + options: { + contentType: 'application/json', + withCredentials: true, + }, + }); + expect(JSON.parse(requests[0].data)).to.deep.include({ + ...videoExchangeRequest, + ext: { + adbook: { + config: DEFAULT_BIDDER_CONFIG, + version: { + adapter: VERSION, + prebid: '$prebid.version$', + }, + }, + }, + }); + }); + + it('should build correct request for video bid with w and h', () => { + const bid = utils.deepClone(videoBid); + + delete bid.mediaTypes.video.playerSize; + + bid.mediaTypes.video.w = 400; + bid.mediaTypes.video.h = 300; + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = JSON.parse(request.data); + + expect(requestData.imp[0].video.w).to.equal(400); + expect(requestData.imp[0].video.h).to.equal(300); + }); + + it('should build correct request for video bid with both w, h and playerSize', () => { + const bid = utils.deepClone(videoBid); + + bid.mediaTypes.video.w = 640; + bid.mediaTypes.video.h = 480; + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = JSON.parse(request.data); + + expect(requestData.imp[0].video.w).to.equal(640); + expect(requestData.imp[0].video.h).to.equal(480); + }); + + it('should build correct request for mixed bid', () => { + sandbox + .stub(common, 'getConfig') + .withArgs('adbookpsp.orgId') + .returns(undefined) + .withArgs('adbookpsp.exchangeUrl') + .returns('https://ex.fattail.com/openrtb2'); + + const requests = spec.buildRequests([mixedBid], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0]).to.deep.include({ + method: 'POST', + url: 'https://ex.fattail.com/openrtb2', + options: { + contentType: 'application/json', + withCredentials: true, + }, + }); + expect(JSON.parse(requests[0].data)).to.deep.include( + mixedExchangeRequest + ); + }); + + it('should use orgId from config', () => { + const bid = utils.deepClone(bannerBid); + + delete bid.params; + + sandbox + .stub(common, 'getConfig') + .withArgs('adbookpsp.orgId') + .returns('129576'); + + const requests = spec.buildRequests([bid], bidderRequest); + const request = JSON.parse(requests[0].data); + + expect(request.imp[0].ext).to.deep.include({ + adbook: { + orgId: '129576', + }, + }); + }); + + it('should use orgId from adUnit when orgId is also set in config', () => { + const bid = utils.deepClone(bannerBid); + + delete bid.params.placementId; + + bid.params.orgId = 'adUnitOrgId'; + + sandbox + .stub(common, 'getConfig') + .withArgs('adbookpsp.orgId') + .returns('configOrgId'); + + const requests = spec.buildRequests([bid], bidderRequest); + const request = JSON.parse(requests[0].data); + + expect(request.imp[0].ext).to.deep.include({ + adbook: { + orgId: 'adUnitOrgId', + }, + }); + }); + + it('should include in request GDPR options if available', () => { + const request = utils.deepClone(bidderRequest); + + delete request.uspConsent; + + const requests = spec.buildRequests([bannerBid, mixedBid], request); + + expect(JSON.parse(requests[0].data)).to.deep.include({ + regs: { + coppa: 0, + ext: { + gdpr: 1, + gdprConsentString: 'gdprConsentString', + }, + }, + }); + }); + + it('should include in request USP (CPPA) options if available', () => { + const request = utils.deepClone(bidderRequest); + + delete request.gdprConsent; + + const requests = spec.buildRequests([bannerBid, mixedBid], request); + + expect(JSON.parse(requests[0].data)).to.deep.include({ + regs: { + coppa: 0, + ext: { + us_privacy: 'uspConsentString', + }, + }, + }); + }); + + it('should pass valid coppa flag based on config', () => { + sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); + + const request = utils.deepClone(bidderRequest); + + delete request.gdprConsent; + delete request.uspConsent; + + const requests = spec.buildRequests([bannerBid, mixedBid], request); + + expect(JSON.parse(requests[0].data)).to.deep.include({ + regs: { + coppa: 1, + }, + }); + }); + + it('should pass GDPR, USP (CCPA) and COPPA options', () => { + sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); + + const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); + + expect(JSON.parse(requests[0].data)).to.deep.include({ + regs: { + coppa: 1, + ext: { + gdpr: 1, + gdprConsentString: 'gdprConsentString', + us_privacy: 'uspConsentString', + }, + }, + }); + }); + + it('should generate and pass user id when is not present in cookie and local storage is not enabled', () => { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); + const rtbRequest = JSON.parse(requests[0].data); + + expect(rtbRequest.user.id).to.have.lengthOf(36); + }); + + it('should pass user id when is present in cookie', () => { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + sandbox + .stub(storage, 'getCookie') + .returns('e35da6bb-f2f8-443b-aeff-3375bef45c9d'); + const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); + const rtbRequest = JSON.parse(requests[0].data); + + expect(rtbRequest.user.id).to.equal( + 'e35da6bb-f2f8-443b-aeff-3375bef45c9d' + ); + }); + + it('should pass user id if is present in local storage', () => { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox + .stub(storage, 'getDataFromLocalStorage') + .returns('e35da6bb-f2f8-443b-aeff-3375bef45c9d'); + + const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); + const rtbRequest = JSON.parse(requests[0].data); + expect(rtbRequest.user.id).to.equal( + 'e35da6bb-f2f8-443b-aeff-3375bef45c9d' + ); + }); + + it('should regenerate user id if it is invalid', () => { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage').returns('foo'); + + const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); + const rtbRequest = JSON.parse(requests[0].data); + expect(rtbRequest.user.id).to.have.lengthOf(36); + }); + + it('should pass schain if available', () => { + const bid = utils.deepClone(bannerBid); + const schain = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com', + }, + ], + }; + + bid.schain = schain; + + const requests = spec.buildRequests([bid], bidderRequest); + + expect(JSON.parse(requests[0].data).source).to.deep.include({ + ext: { + schain, + }, + }); + }); + + it('return empty array if there are no valid bid requests', () => { + const requests = spec.buildRequests([], bidderRequest); + + expect(requests).to.deep.equal([]); + }); + + it('should prioritize device information set in config', () => { + const ua = + 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1'; + + sandbox.stub(common, 'getConfig').withArgs('device').returns({ + ua, + }); + + const requests = spec.buildRequests([bannerBid], bidderRequest); + + expect(JSON.parse(requests[0].data).device.ua).to.equal(ua); + }); + + it('should include bidder config', () => { + const bidderConfig = { + bidTTL: 500, + defaultCurrency: 'USD', + exchangeUrl: 'https://exsb.fattail.com/openrtb2', + winTrackingEnabled: true, + winTrackingUrl: 'https://evsb.fattail.com/wins', + orgId: '129576', + }; + sandbox + .stub(common, 'getConfig') + .withArgs('adbookpsp') + .returns(bidderConfig); + + const requests = spec.buildRequests([bannerBid], bidderRequest); + const request = JSON.parse(requests[0].data); + + expect(request.ext).to.deep.include({ + adbook: { + config: bidderConfig, + version: { + adapter: VERSION, + prebid: '$prebid.version$', + }, + }, + }); + }); + + it('should use bidder video params if they are set', () => { + const videoBidWithParams = utils.deepClone(videoBid); + const bidderVideoParams = { + api: [1, 2], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [3, 4], + protocols: [5, 6], + minduration: 10, + maxduration: 30, + }; + videoBidWithParams.params.video = bidderVideoParams; + + const requests = spec.buildRequests([videoBidWithParams], bidderRequest); + const request = JSON.parse(requests[0].data); + + expect(request.imp[0]).to.deep.include({ + video: { + ...bidderVideoParams, + w: videoBidWithParams.mediaTypes.video.playerSize[0][0], + h: videoBidWithParams.mediaTypes.video.playerSize[0][1], + }, + }); + }); + }); + + describe('interpretResponse()', () => { + it('should correctly interpret valid response', () => { + sandbox + .stub(common, 'getConfig') + .withArgs('adbookpsp.defaultCurrency') + .returns(DEFAULT_BIDDER_CONFIG.defaultCurrency) + .withArgs('adbookpsp.bidTTL') + .returns(DEFAULT_BIDDER_CONFIG.bidTTL); + + const response = utils.deepClone(exchangeResponse); + const bids = spec.interpretResponse( + { body: response }, + { data: JSON.stringify(exchangeBidRequest) } + ); + + expect(bids).to.deep.equal([ + { + bidderRequestId: '999ccceeee11', + requestId: '9873kfse', + bidId: 'bid123456', + width: 300, + height: 250, + ttl: 300, + cpm: 0.5, + currency: 'USD', + creativeId: '123456789', + mediaType: 'banner', + meta: { + advertiserDomains: ['advertiser.com'], + mediaType: 'banner', + primaryCatId: 'IAB2-1', + secondaryCatIds: ['IAB2-2', 'IAB2-3'], + }, + netRevenue: true, + nurl: 'http://win.example.url', + adUnitCode: 'div-gpt-ad-837465923534-0', + ad: '
ad
', + adId: '5', + adserverTargeting: { + hb_ad_ord_adbookpsp: '0_0', // the value to the left of the underscore represents the index of the ad id and the number to the right represents the order index + hb_adid_c_adbookpsp: '5', + hb_deal_adbookpsp: 'werwetwerw', + hb_liid_adbookpsp: '2342345', + hb_ordid_adbookpsp: '567843', + }, + referrer: 'http://prebid-test-page.io:8080/banner.html', + lineItemId: '2342345', + }, + { + ad: '', + adId: '10', + adUnitCode: 'div-gpt-ad-837465923534-0', + adserverTargeting: { + hb_ad_ord_adbookpsp: '0_0', + hb_adid_c_adbookpsp: '10', + hb_deal_adbookpsp: 'dsfxcxcvxc', + hb_liid_adbookpsp: '2121221', + hb_ordid_adbookpsp: '5678234', + }, + bidId: 'bid4321', + bidderRequestId: '999ccceeee11', + cpm: 0.45, + creativeId: '543123', + currency: 'USD', + height: 250, + lineItemId: '2121221', + mediaType: 'video', + meta: { + advertiserDomains: ['advertiser.com', 'campaign.advertiser.com'], + mediaType: 'video', + primaryCatId: 'IAB2-3', + secondaryCatIds: [], + }, + netRevenue: true, + nurl: 'http://win.example.url', + referrer: 'http://prebid-test-page.io:8080/banner.html', + requestId: '120kfeske', + ttl: 300, + vastXml: + '', + width: 300, + }, + ]); + }); + + it('should place valid GAM targeting for all bids when multiple bids are present for multiple impressions', () => { + const response = utils.deepClone(exchangeResponse); + + const bids = spec.interpretResponse( + { body: response }, + { data: JSON.stringify(exchangeBidRequest) } + ); + + expect(bids).to.have.length(2); + expect(bids[0].adserverTargeting).to.deep.equal({ + hb_ad_ord_adbookpsp: '0_0', + hb_adid_c_adbookpsp: '5', + hb_deal_adbookpsp: 'werwetwerw', + hb_liid_adbookpsp: '2342345', + hb_ordid_adbookpsp: '567843', + }); + expect(bids[1].adserverTargeting).to.deep.equal({ + hb_ad_ord_adbookpsp: '0_0', + hb_adid_c_adbookpsp: '10', + hb_deal_adbookpsp: 'dsfxcxcvxc', + hb_liid_adbookpsp: '2121221', + hb_ordid_adbookpsp: '5678234', + }); + }); + + it('should place valid GAM targeting for all bids when multiple bids are present for single impression', () => { + const response = utils.deepClone(exchangeResponse); + + response.seatbid[1].bid[0].impid = '9873kfse'; + + const bids = spec.interpretResponse( + { body: response }, + { data: JSON.stringify(exchangeBidRequest) } + ); + + expect(bids).to.have.length(2); + for (const bid of bids) { + expect(bid.adserverTargeting).to.deep.equal({ + hb_ad_ord_adbookpsp: '0_0,1_0', + hb_adid_c_adbookpsp: '5,10', + hb_deal_adbookpsp: 'werwetwerw,dsfxcxcvxc', + hb_liid_adbookpsp: '2342345,2121221', + hb_ordid_adbookpsp: '567843,5678234', + }); + } + }); + + it('should return no bids if response id does not match bidderRequestId', () => { + const body = utils.deepClone(exchangeResponse); + body.id = '999'; + + const bids = spec.interpretResponse( + { body }, + { data: JSON.stringify(exchangeBidRequest) } + ); + + expect(bids).to.deep.equal([]); + }); + + it('should return no bids if response does not include seatbid', () => { + const body = utils.deepClone(exchangeResponse); + delete body.seatbid; + + const bids = spec.interpretResponse( + { body }, + { data: JSON.stringify(exchangeBidRequest) } + ); + + expect(bids).to.deep.equal([]); + }); + + it('should return no bids if response does not include any bids', () => { + const body = utils.deepClone(exchangeResponse); + body.seatbid = []; + + const bids = spec.interpretResponse( + { body }, + { data: JSON.stringify(exchangeBidRequest) } + ); + + expect(bids).to.deep.equal([]); + }); + + it('should exclude invalid video bids', () => { + const body = utils.deepClone(exchangeResponse); + + body.seatbid.shift(); + body.seatbid[0].bid[0].adid = 34; + + const bids = spec.interpretResponse( + { body }, + { data: JSON.stringify(exchangeBidRequest) } + ); + + expect(bids).to.deep.equal([]); + }); + + it('should exclude invalid banner bids', () => { + const body = utils.deepClone(exchangeResponse); + const request = utils.deepClone(exchangeBidRequest); + + body.seatbid.pop(); + + delete body.seatbid[0].bid[0].w; + delete body.seatbid[0].bid[0].h; + + request.imp[0].banner.format.push({ w: 300, h: 600 }); + + const bids = spec.interpretResponse( + { body }, + { data: JSON.stringify(request) } + ); + + expect(bids).to.deep.equal([]); + }); + + it('should not include invalid banner bids in targeting map', () => { + const body = utils.deepClone(exchangeResponse); + const request = utils.deepClone(exchangeBidRequest); + + body.seatbid[0].bid[0].h = '600'; + + request.imp[0].banner.format.push({ w: 300, h: 600 }); + + const bids = spec.interpretResponse( + { body }, + { data: JSON.stringify(exchangeBidRequest) } + ); + + expect(bids[0].adserverTargeting).to.deep.equal({ + hb_ad_ord_adbookpsp: '0_0', + hb_adid_c_adbookpsp: '10', + hb_deal_adbookpsp: 'dsfxcxcvxc', + hb_liid_adbookpsp: '2121221', + hb_ordid_adbookpsp: '5678234', + }); + }); + + it('should not validate banner bid dimensions if bid request has single size', () => { + const body = utils.deepClone(exchangeResponse); + const request = utils.deepClone(exchangeBidRequest); + + delete body.seatbid[1]; + delete body.seatbid[0].bid[0].h; + delete body.seatbid[0].bid[0].w; + + const bids = spec.interpretResponse( + { body }, + { data: JSON.stringify(request) } + ); + + expect(bids.length).to.equal(1); + }); + }); + + describe('getUserSyncs()', () => { + it('should return user syncs if there are included in the response and syncs are enabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: true, + iframeEnabled: true, + }, + [{ body: exchangeResponse }] + ); + + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'http://sometest.com/sync/1234567', + }, + { + type: 'iframe', + url: 'http://sometest.com/sync/1234567', + }, + ]); + }); + + it('should not return user syncs if syncs are disabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: false, + iframeEnabled: false, + }, + [{ body: exchangeResponse }] + ); + + expect(syncs).to.deep.equal([]); + }); + + it('should return image syncs if they are enabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: true, + iframeEnabled: false, + }, + [{ body: exchangeResponse }] + ); + + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'http://sometest.com/sync/1234567', + }, + ]); + }); + + it('should return iframe syncs if they are enabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: false, + iframeEnabled: true, + }, + [{ body: exchangeResponse }] + ); + + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'http://sometest.com/sync/1234567', + }, + ]); + }); + + it('should append COPPA status to sync url', () => { + sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); + const syncs = spec.getUserSyncs( + { + pixelEnabled: false, + iframeEnabled: true, + }, + [{ body: utils.deepClone(exchangeResponse) }] + ); + + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'http://sometest.com/sync/1234567?coppa=1', + }, + ]); + }); + + it('should append GDPR consent data to url', () => { + sandbox.stub(common, 'getConfig').withArgs('coppa').returns(false); + const syncs = spec.getUserSyncs( + { + pixelEnabled: false, + iframeEnabled: true, + }, + [{ body: utils.deepClone(exchangeResponse) }], + { gdprApplies: true, consentString: 'gdprConsentString' } + ); + + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString', + }, + ]); + }); + + it('should append USP (CCPA) consent string to url', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: false, + iframeEnabled: true, + }, + [{ body: utils.deepClone(exchangeResponse) }], + undefined, + 'uspConsentString' + ); + + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'http://sometest.com/sync/1234567?us_privacy=uspConsentString', + }, + ]); + }); + + it('should append COPPA, GDPR and USP (CCPA) url params', () => { + sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); + const syncs = spec.getUserSyncs( + { + pixelEnabled: true, + iframeEnabled: true, + }, + [{ body: utils.deepClone(exchangeResponse) }], + { gdprApplies: true, consentString: 'gdprConsentString' }, + 'uspConsentString' + ); + + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', + }, + { + type: 'iframe', + url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', + }, + ]); + }); + + it('should respect url param syntax when appending params', () => { + sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); + + const response = utils.deepClone(exchangeResponse); + + response.ext.sync[0] = { + type: 'image', + url: 'http://sometest.com/sync/1234567?horseCount=4', + }; + + const syncs = spec.getUserSyncs( + { + pixelEnabled: true, + iframeEnabled: false, + }, + [{ body: response }], + { gdprApplies: true, consentString: 'gdprConsentString' }, + 'uspConsentString' + ); + + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'http://sometest.com/sync/1234567?horseCount=4&gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', + }, + ]); + }); + }); + + describe('onBidWon()', () => { + it('should track win if win tracking is enabled', () => { + const spy = sandbox.spy(utils, 'triggerPixel'); + + sandbox + .stub(common, 'getConfig') + .withArgs('adbookpsp.winTrackingEnabled') + .returns(true) + .withArgs('adbookpsp.winTrackingUrl') + .returns('https://ev.fattail.com/wins'); + + spec.onBidWon({ + requestId: 'requestId', + bidderRequestId: 'bidderRequestId', + bidId: 'bidId', + }); + + expect( + spy.calledWith( + 'https://ev.fattail.com/wins?impId=requestId&reqId=bidderRequestId&bidId=bidId' + ) + ).to.equal(true); + }); + it('should call bid.nurl if win tracking is enabled', () => { + const spy = sandbox.spy(utils, 'triggerPixel'); + + sandbox + .stub(common, 'getConfig') + .withArgs('adbookpsp.winTrackingEnabled') + .returns(true) + .withArgs('adbookpsp.winTrackingUrl') + .returns('https://ev.fattail.com/wins'); + + spec.onBidWon({ + requestId: 'requestId', + bidderRequestId: 'bidderRequestId', + bidId: 'bidId', + nurl: 'http://win.example.url', + }); + + expect(spy.calledWith('http://win.example.url')).to.equal(true); + }); + it('should not track win nor call bid.nurl if win tracking is disabled', () => { + const spy = sandbox.spy(utils, 'triggerPixel'); + + sandbox + .stub(common, 'getConfig') + .withArgs('adbookpsp.winTrackingEnabled') + .returns(false) + .withArgs('adbookpsp.winTrackingUrl') + .returns('https://ev.fattail.com/wins'); + + spec.onBidWon({ + requestId: 'requestId', + bidderRequestId: 'bidderRequestId', + bidId: 'bidId', + nurl: 'http://win.example.url', + }); + + expect(spy.notCalled).to.equal(true); + }); + }); +}); + +const bidderRequest = { + auctionId: 'aaccee333311', + bidderRequestId: '999ccceeee11', + timeout: 200, + refererInfo: { + referer: 'http://example-domain.com/foo', + }, + gdprConsent: { + gdprApplies: 1, + consentString: 'gdprConsentString', + }, + uspConsent: 'uspConsentString', +}; + +const bannerBid = { + bidder: 'adbookpsp', + params: { + placementId: '12390123', + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + adUnitCode: 'div-gpt-ad-837465923534-0', + transactionId: 'sfsf89e-mck3-asf3-fe45-feksjfi123mfs', + bidId: '9873kfse', + bidderRequestId: '999ccceeee11', + auctionId: 'aaccee333311', + lineItemId: 123123123, +}; + +const bannerExchangeRequest = { + id: '999ccceeee11', + device: { + h: 100, + w: 100, + js: true, + ua: navigator.userAgent, + dnt: 0, + }, + regs: { + coppa: 0, + ext: { + gdpr: 1, + gdprConsentString: 'gdprConsentString', + us_privacy: 'uspConsentString', + }, + }, + site: { + domain: location.hostname, + page: location.href, + ref: 'http://example-domain.com/foo', + }, + source: { + fd: 1, + tid: 'aaccee333311', + }, + tmax: 200, + user: { + gdprConsentString: 'gdprConsentString', + id: '54444444-5444-4444-9444-544444444444', + }, + imp: [ + { + banner: { + format: [ + { + w: 300, + h: 250, + }, + { + w: 300, + h: 600, + }, + ], + w: 300, + h: 250, + topframe: 0, + pos: 0, + }, + ext: { + adbook: { + placementId: '12390123', + }, + }, + id: '9873kfse', + tagid: 'div-gpt-ad-837465923534-0', + }, + ], + ext: { + adbook: { + version: { + adapter: VERSION, + prebid: '$prebid.version$', + }, + }, + }, +}; + +const videoBid = { + bidder: 'adbookpsp', + params: { + placementId: '129576', + }, + mediaTypes: { + video: { + api: [1, 2, 4, 6], + mimes: ['video/mp4'], + playbackmethod: [2, 4, 6], + playerSize: [[400, 300]], + protocols: [3, 4, 7, 8, 10], + }, + }, + adUnitCode: 'div-gpt-ad-9383743831-6', + transactionId: 'aacc3fasf-fere-1335-8m1s-785393mc3fj', + bidId: '120kfeske', + bidderRequestId: '999ccceeee11', + auctionId: 'aaccee333311', + lineItemId: 321321321, +}; + +const videoExchangeRequest = { + id: '999ccceeee11', + device: { + h: 100, + w: 100, + js: true, + ua: navigator.userAgent, + dnt: 0, + }, + regs: { + coppa: 0, + ext: { + gdpr: 1, + gdprConsentString: 'gdprConsentString', + us_privacy: 'uspConsentString', + }, + }, + site: { + domain: location.hostname, + page: location.href, + ref: 'http://example-domain.com/foo', + }, + source: { + fd: 1, + tid: 'aaccee333311', + }, + tmax: 200, + user: { + gdprConsentString: 'gdprConsentString', + id: '54444444-5444-4444-9444-544444444444', + }, + imp: [ + { + video: { + api: [1, 2, 4, 6], + h: 300, + mimes: ['video/mp4'], + playbackmethod: [2, 4, 6], + protocols: [3, 4, 7, 8, 10], + w: 400, + }, + ext: { + adbook: { + placementId: '129576', + }, + }, + id: '120kfeske', + tagid: 'div-gpt-ad-9383743831-6', + }, + ], + ext: { + adbook: { + version: { + adapter: VERSION, + prebid: '$prebid.version$', + }, + }, + }, +}; + +const mixedBid = { + bidder: 'adbookpsp', + params: { + orgId: '129576', + }, + mediaTypes: { + banner: { + sizes: [[300, 600]], + }, + video: { + mimes: ['video/mp4'], + playerSize: [[300, 600]], + }, + }, + adUnitCode: 'div-gpt-ad-9383743831-5', + transactionId: 'aacc3fasf-fere-1335-8m1s-785393mc3fj', + bidId: '120kfeske', + bidderRequestId: '999ccceeee11', + auctionId: 'aaccee333311', + lineItemId: 12341234, +}; + +const mixedExchangeRequest = { + id: '999ccceeee11', + device: { + h: 100, + w: 100, + js: true, + ua: navigator.userAgent, + dnt: 0, + }, + regs: { + coppa: 0, + ext: { + gdpr: 1, + gdprConsentString: 'gdprConsentString', + us_privacy: 'uspConsentString', + }, + }, + site: { + domain: location.hostname, + page: location.href, + ref: 'http://example-domain.com/foo', + }, + source: { + fd: 1, + tid: 'aaccee333311', + }, + tmax: 200, + user: { + gdprConsentString: 'gdprConsentString', + id: '54444444-5444-4444-9444-544444444444', + }, + imp: [ + { + banner: { + format: [ + { + w: 300, + h: 600, + }, + ], + w: 300, + h: 600, + topframe: 0, + pos: 0, + }, + video: { + h: 600, + mimes: ['video/mp4'], + w: 300, + }, + ext: { + adbook: { + orgId: '129576', + }, + }, + id: '120kfeske', + tagid: 'div-gpt-ad-9383743831-5', + }, + ], + ext: { + adbook: { + version: { + adapter: VERSION, + prebid: '$prebid.version$', + }, + }, + }, +}; + +const exchangeBidRequest = { + id: '999ccceeee11', + tmax: 200, + imp: [ + { + id: '9873kfse', + banner: { + format: [ + { + w: 300, + h: 250, + }, + ], + }, + video: { + w: 300, + h: 250, + }, + tagid: 'div-gpt-ad-837465923534-0', + }, + { + id: '120kfeske', + banner: { + format: [ + { + w: 300, + h: 250, + }, + ], + }, + video: { + w: 300, + h: 250, + }, + tagid: 'div-gpt-ad-837465923534-0', + }, + ], + source: { + fd: 1, + tid: 'aaccee333311', + }, + site: { + domain: location.hostname, + page: location.href, + ref: 'http://prebid-test-page.io:8080/banner.html', + }, +}; + +const exchangeResponse = { + id: '999ccceeee11', + seatbid: [ + { + seat: 'adbookpsp', + group: 0, + bid: [ + { + id: 'bid123456', + w: 300, + h: 250, + impid: '9873kfse', + price: 0.5, + exp: 300, + crid: '123456789', + adm: '
ad
', + adid: '5', + dealid: 'werwetwerw', + nurl: 'http://win.example.url', + ext: { + liid: '2342345', + ordid: '567843', + }, + cat: ['IAB2-1', 'IAB2-2', 'IAB2-3'], + adomain: ['advertiser.com'], + }, + ], + }, + { + seat: 'adbookpsp', + group: 0, + bid: [ + { + id: 'bid4321', + impid: '120kfeske', + price: 0.45, + exp: 300, + crid: '543123', + adm: '', + adid: '10', + dealid: 'dsfxcxcvxc', + nurl: 'http://win.example.url', + ext: { + liid: '2121221', + ordid: '5678234', + }, + cat: ['IAB2-3'], + adomain: ['advertiser.com', 'campaign.advertiser.com'], + }, + ], + }, + ], + ext: { + sync: [ + { + type: 'image', + url: 'http://sometest.com/sync/1234567', + }, + { + type: 'iframe', + url: 'http://sometest.com/sync/1234567', + }, + ], + }, +}; diff --git a/test/spec/modules/adbutlerBidAdapter_spec.js b/test/spec/modules/adbutlerBidAdapter_spec.js deleted file mode 100644 index 6dedce321d8..00000000000 --- a/test/spec/modules/adbutlerBidAdapter_spec.js +++ /dev/null @@ -1,231 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/adbutlerBidAdapter.js'; - -describe('AdButler adapter', function () { - let bidRequests; - - beforeEach(function () { - bidRequests = [ - { - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093', - keyword: 'red', - minCPM: '1.00', - maxCPM: '5.00', - extra: { - foo: 'bar', - } - }, - placementCode: '/19968336/header-bid-tag-1', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - }, - }, - bidId: '23acc48ad47af5', - auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ]; - }); - - describe('implementation', function () { - describe('for requests', function () { - it('should accept valid bid', function () { - let validBid = { - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093' - } - }, - isValid = spec.isBidRequestValid(validBid); - - expect(isValid).to.equal(true); - }); - - it('should reject invalid bid', function () { - let invalidBid = { - bidder: 'adbutler', - params: { - accountID: '167283', - } - }, - isValid = spec.isBidRequestValid(invalidBid); - - expect(isValid).to.equal(false); - }); - - it('should use custom domain string', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '107878', - zoneID: '86133', - domain: 'servedbyadbutler.com.dan.test' - }, - auctionId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - ], - requests = spec.buildRequests(bidRequests), - requestURL = requests[0].url; - - expect(requestURL).to.have.string('.dan.test'); - }); - - it('should set default domain', function () { - let requests = spec.buildRequests(bidRequests), - request = requests[0]; - - let [domain] = request.url.split('/adserve/'); - - expect(domain).to.equal('https://servedbyadbutler.com'); - }); - - it('should set the keyword parameter', function () { - let requests = spec.buildRequests(bidRequests), - requestURL = requests[0].url; - - expect(requestURL).to.have.string(';kw=red;'); - }); - - it('should set the extra parameter', () => { - let requests = spec.buildRequests(bidRequests); - let requestURL = requests[0].url; - - expect(requestURL).to.have.string(';foo=bar;'); - }); - - it('should increment the count for the same zone', function () { - let bidRequests = [ - { - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '107878', - zoneID: '86133', - } - }, { - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '107878', - zoneID: '86133', - } - }, - ], - requests = spec.buildRequests(bidRequests), - firstRequest = requests[0].url, - secondRequest = requests[1].url; - - expect(firstRequest).to.have.string(';place=0;'); - expect(secondRequest).to.have.string(';place=1;'); - }); - }); - - describe('bid responses', function () { - it('should return complete bid response', function () { - let serverResponse = { - body: { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 1.5, - width: 300, - height: 250, - place: 0, - ad_code: '', - tracking_pixels: [ - 'http://tracking.pixel.com/params=info' - ] - } - }, - bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); - - expect(bids).to.be.lengthOf(1); - - expect(bids[0].bidderCode).to.equal('adbutler'); - expect(bids[0].cpm).to.equal(1.5); - expect(bids[0].width).to.equal(300); - expect(bids[0].height).to.equal(250); - expect(bids[0].currency).to.equal('USD'); - expect(bids[0].netRevenue).to.equal(true); - expect(bids[0].ad).to.have.length.above(1); - expect(bids[0].ad).to.have.string('http://tracking.pixel.com/params=info'); - }); - - it('should return empty bid response', function () { - let serverResponse = { - body: { - status: 'NO_ELIGIBLE_ADS', - zone_id: 210083, - width: 300, - height: 250, - place: 0 - } - }, - bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); - - expect(bids).to.be.lengthOf(0); - }); - - it('should return empty bid response on incorrect size', function () { - let serverResponse = { - body: { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210083, - cpm: 1.5, - width: 728, - height: 90, - place: 0 - } - }, - bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); - - expect(bids).to.be.lengthOf(0); - }); - - it('should return empty bid response with CPM too low', function () { - let serverResponse = { - body: { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 0.75, - width: 300, - height: 250, - place: 0 - } - }, - bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); - - expect(bids).to.be.lengthOf(0); - }); - - it('should return empty bid response with CPM too high', function () { - let serverResponse = { - body: { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 7, - width: 300, - height: 250, - place: 0 - } - }, - bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); - - expect(bids).to.be.lengthOf(0); - }); - }); - }); -}); diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index 2c141d31bf8..ed096e7189d 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -1,12 +1,10 @@ // jshint esversion: 6, es3: false, node: true -import {assert, expect} from 'chai'; -import {spec} from 'modules/adfBidAdapter.js'; -import { NATIVE } from 'src/mediaTypes.js'; +import { assert } from 'chai'; +import { spec } from 'modules/adfBidAdapter.js'; import { config } from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; describe('Adf adapter', function () { - let serverResponse, bidRequest, bidResponses; let bids = []; describe('backwards-compatibility', function () { @@ -14,6 +12,11 @@ describe('Adf adapter', function () { assert.equal(spec.aliases[0].code, 'adformOpenRTB'); assert.equal(spec.aliases[0].gvlid, 50); }); + + it('should have adform alias defined', function () { + assert.equal(spec.aliases[1].code, 'adform'); + assert.equal(spec.aliases[1].gvlid, 50); + }); }); describe('isBidRequestValid', function () { @@ -26,20 +29,45 @@ describe('Adf adapter', function () { it('should return true when required params found', function () { assert(spec.isBidRequestValid(bid)); + + bid.params = { + inv: 1234, + mname: 'some-placement' + }; + assert(spec.isBidRequestValid(bid)); + + bid.params = { + mid: 4332, + inv: 1234, + mname: 'some-placement' + }; + assert(spec.isBidRequestValid(bid)); }); it('should return false when required params are missing', function () { bid.params = { adxDomain: 'adx.adform.net' }; assert.isFalse(spec.isBidRequestValid(bid)); + + bid.params = { + mname: 'some-placement' + }; + assert.isFalse(spec.isBidRequestValid(bid)); + + bid.params = { + inv: 1234 + }; + assert.isFalse(spec.isBidRequestValid(bid)); }); }); describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); + }); it('should send request with correct structure', function () { let validBidRequests = [{ bidId: 'bidId', params: { - siteId: 'siteId', adxDomain: '10.8.57.207' } }]; @@ -53,7 +81,7 @@ describe('Adf adapter', function () { describe('user privacy', function () { it('should send GDPR Consent data to adform if gdprApplies', function () { - let validBidRequests = [{ bidId: 'bidId', params: { siteId: 'siteId', test: 1 } }]; + let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { referer: 'page' } }; let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); @@ -63,7 +91,7 @@ describe('Adf adapter', function () { }); it('should send gdpr as number', function () { - let validBidRequests = [{ bidId: 'bidId', params: { siteId: 'siteId', test: 1 } }]; + let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { referer: 'page' } }; let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); @@ -72,7 +100,7 @@ describe('Adf adapter', function () { }); it('should send CCPA Consent data to adform', function () { - let validBidRequests = [{ bidId: 'bidId', params: { siteId: 'siteId', test: 1 } }]; + let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; let bidderRequest = { uspConsent: '1YA-', refererInfo: { referer: 'page' } }; let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); @@ -118,7 +146,7 @@ describe('Adf adapter', function () { it('should add test and is_debug to request, if test is set in parameters', function () { let validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId', test: 1 } + params: { test: 1 } }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); @@ -151,26 +179,66 @@ describe('Adf adapter', function () { }); it('should send info about device', function () { + config.setConfig({ + device: { w: 100, h: 100 } + }); let validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId' } + params: { mid: '1000' } }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.device.w, 100); + assert.equal(request.device.h, 100); }); + + it('should send app info', function () { + config.setConfig({ + app: { id: 'appid' }, + ortb2: { app: { name: 'appname' } } + }); + let validBidRequests = [{ + bidId: 'bidId', + params: { mid: '1000' } + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.equal(request.app.id, 'appid'); + assert.equal(request.app.name, 'appname'); + assert.equal(request.site, undefined); + }); + it('should send info about the site', function () { + config.setConfig({ + site: { + id: '123123', + publisher: { + domain: 'publisher.domain.com' + } + }, + ortb2: { + site: { + publisher: { + name: 'publisher\'s name' + } + } + } + }); let validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId', publisher: {id: '123123', domain: 'publisher.domain.com', name: 'publisher\'s name'} } + params: { mid: '1000' } }]; let refererInfo = { referer: 'page' }; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo }).data); assert.deepEqual(request.site, { page: refererInfo.referer, - publisher: validBidRequests[0].params.publisher, - id: validBidRequests[0].params.siteId + publisher: { + domain: 'publisher.domain.com', + name: 'publisher\'s name' + }, + id: '123123' }); }); @@ -200,6 +268,27 @@ describe('Adf adapter', function () { assert.deepEqual(request.cur, [ 'EUR' ]); }); + it('should pass supply chain object', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {}, + schain: { + validation: 'strict', + config: { + ver: '1.0' + } + } + }]; + + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + assert.deepEqual(request.source.ext.schain, { + validation: 'strict', + config: { + ver: '1.0' + } + }); + }); + describe('priceType', function () { it('should send default priceType', function () { let validBidRequests = [{ @@ -213,7 +302,7 @@ describe('Adf adapter', function () { it('should send correct priceType value', function () { let validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId', priceType: 'net' } + params: { priceType: 'net' } }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); @@ -237,13 +326,16 @@ describe('Adf adapter', function () { it('should add incrementing values of id', function () { let validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId' } + params: { mid: '1000' }, + mediaTypes: {video: {}} }, { bidId: 'bidId2', - params: { siteId: 'siteId' } + params: { mid: '1000' }, + mediaTypes: {video: {}} }, { bidId: 'bidId3', - params: { siteId: 'siteId' } + params: { mid: '1000' }, + mediaTypes: {video: {}} }]; let imps = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp; @@ -253,21 +345,184 @@ describe('Adf adapter', function () { }); it('should add mid', function () { - let validBidRequests = [{ bidId: 'bidId', params: { siteId: 'siteId', mid: 1000 } }, - { bidId: 'bidId2', params: { siteId: 'siteId', mid: 1001 } }, - { bidId: 'bidId3', params: { siteId: 'siteId', mid: 1002 } }]; + let validBidRequests = [{ bidId: 'bidId', params: {mid: 1000}, mediaTypes: {video: {}} }, + { bidId: 'bidId2', params: {mid: 1001}, mediaTypes: {video: {}} }, + { bidId: 'bidId3', params: {mid: 1002}, mediaTypes: {video: {}} }]; let imps = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp; for (let i = 0; i < 3; i++) { assert.equal(imps[i].tagid, validBidRequests[i].params.mid); } }); + describe('dynamic placement tag', function () { + it('should add imp parameters correctly', function () { + const validBidRequests = [ + { bidId: 'bidId', params: { inv: 1000, mname: 'placement' }, mediaTypes: {video: {}} }, + { bidId: 'bidId', params: { mid: 1234, inv: 1002, mname: 'placement2' }, mediaTypes: {video: {}} }, + { bidId: 'bidId', params: { mid: 1234 }, mediaTypes: {video: {}} } + ]; + const [ imp1, imp2, imp3 ] = getRequestImps(validBidRequests); + + assert.equal(imp1.ext.bidder.inv, 1000); + assert.equal(imp1.ext.bidder.mname, 'placement'); + assert.equal('tagid' in imp1, false); + + assert.equal(imp2.ext.bidder.inv, 1002); + assert.equal(imp2.ext.bidder.mname, 'placement2'); + assert.equal(imp2.tagid, 1234); + + assert.ok(imp3.ext.bidder); + assert.equal('inv' in imp3.ext.bidder, false); + assert.equal('mname' in imp3.ext.bidder, false); + assert.equal(imp3.tagid, 1234); + }); + }); + + describe('price floors', function () { + it('should not add if floors module not configured', function () { + const validBidRequests = [{ bidId: 'bidId', params: {mid: 1000}, mediaTypes: {video: {}} }]; + let imp = getRequestImps(validBidRequests)[0]; + + assert.equal(imp.bidfloor, undefined); + assert.equal(imp.bidfloorcur, undefined); + }); + + it('should not add if floor price not defined', function () { + const validBidRequests = [ getBidWithFloor() ]; + let imp = getRequestImps(validBidRequests)[0]; + + assert.equal(imp.bidfloor, undefined); + assert.equal(imp.bidfloorcur, 'USD'); + }); + + it('should request floor price in adserver currency', function () { + config.setConfig({ currency: { adServerCurrency: 'DKK' } }); + const validBidRequests = [ getBidWithFloor() ]; + let imp = getRequestImps(validBidRequests)[0]; + + assert.equal(imp.bidfloor, undefined); + assert.equal(imp.bidfloorcur, 'DKK'); + }); + + it('should add correct floor values', function () { + const expectedFloors = [ 1, 1.3, 0.5 ]; + const validBidRequests = expectedFloors.map(getBidWithFloor); + let imps = getRequestImps(validBidRequests); + + expectedFloors.forEach((floor, index) => { + assert.equal(imps[index].bidfloor, floor); + assert.equal(imps[index].bidfloorcur, 'USD'); + }); + }); + + function getBidWithFloor(floor) { + return { + params: { mid: 1 }, + mediaTypes: { video: {} }, + getFloor: ({ currency }) => { + return { + currency: currency, + floor + }; + } + }; + } + }); + + describe('multiple media types', function () { + it('should use all configured media types for bidding', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { mid: 1000 }, + mediaTypes: { + banner: { + sizes: [[100, 100], [200, 300]] + }, + video: {} + } + }, { + bidId: 'bidId1', + params: { mid: 1000 }, + mediaTypes: { + video: {}, + native: {} + } + }, { + bidId: 'bidId2', + params: { mid: 1000 }, + nativeParams: { + title: { required: true, len: 140 } + }, + mediaTypes: { + banner: { + sizes: [[100, 100], [200, 300]] + }, + native: {}, + video: {} + } + }]; + let [ first, second, third ] = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp; + + assert.ok(first.banner); + assert.ok(first.video); + assert.equal(first.native, undefined); + + assert.ok(second.video); + assert.equal(second.banner, undefined); + assert.equal(second.native, undefined); + + assert.ok(third.native); + assert.ok(third.video); + assert.ok(third.banner); + }); + }); + + describe('banner', function () { + it('should convert sizes to openrtb format', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { mid: 1000 }, + mediaTypes: { + banner: { + sizes: [[100, 100], [200, 300]] + } + } + }]; + let { banner } = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0]; + assert.deepEqual(banner, { + format: [ { w: 100, h: 100 }, { w: 200, h: 300 } ] + }); + }); + }); + + describe('video', function () { + it('should pass video mediatype config', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { mid: 1000 }, + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'] + } + } + }]; + let { video } = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0]; + assert.deepEqual(video, { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'] + }); + }); + }); + describe('native', function () { describe('assets', function () { it('should set correct asset id', function () { let validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -283,7 +538,7 @@ describe('Adf adapter', function () { it('should add required key if it is necessary', function () { let validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -303,7 +558,7 @@ describe('Adf adapter', function () { it('should map img and data assets', function () { let validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { title: { required: true, len: 140 }, image: { required: true, sizes: [150, 50] }, @@ -330,7 +585,7 @@ describe('Adf adapter', function () { it('should flatten sizes and utilise first pair', function () { const validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { image: { sizes: [[200, 300], [100, 200]] @@ -348,7 +603,7 @@ describe('Adf adapter', function () { it('should utilise aspect_ratios', function () { const validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { image: { aspect_ratios: [{ @@ -380,7 +635,7 @@ describe('Adf adapter', function () { it('should not throw error if aspect_ratios config is not defined', function () { const validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { image: { aspect_ratios: [] @@ -398,7 +653,7 @@ describe('Adf adapter', function () { it('should expect any dimensions if min_width not passed', function () { const validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { image: { aspect_ratios: [{ @@ -417,6 +672,10 @@ describe('Adf adapter', function () { }); }); }); + + function getRequestImps(validBidRequests) { + return JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp; + } }); describe('interpretResponse', function () { @@ -441,7 +700,7 @@ describe('Adf adapter', function () { bids: [ { bidId: 'bidId1', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -450,7 +709,7 @@ describe('Adf adapter', function () { }, { bidId: 'bidId2', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -482,7 +741,7 @@ describe('Adf adapter', function () { bids: [ { bidId: 'bidId1', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -491,7 +750,7 @@ describe('Adf adapter', function () { }, { bidId: 'bidId2', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -500,7 +759,7 @@ describe('Adf adapter', function () { }, { bidId: 'bidId3', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -509,7 +768,7 @@ describe('Adf adapter', function () { }, { bidId: 'bidId4', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -543,6 +802,13 @@ describe('Adf adapter', function () { assets: [], link: { url: 'link' }, imptrackers: ['imptrackers url1', 'imptrackers url2'] + }, + dealid: 'deal-id', + adomain: [ 'demo.com' ], + ext: { + prebid: { + type: 'native' + } } } ] @@ -555,7 +821,7 @@ describe('Adf adapter', function () { bids: [ { bidId: 'bidId1', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -574,6 +840,9 @@ describe('Adf adapter', function () { assert.deepEqual(bids[0].netRevenue, false); assert.deepEqual(bids[0].currency, serverResponse.body.cur); assert.deepEqual(bids[0].mediaType, 'native'); + assert.deepEqual(bids[0].meta.mediaType, 'native'); + assert.deepEqual(bids[0].meta.advertiserDomains, [ 'demo.com' ]); + assert.deepEqual(bids[0].dealId, 'deal-id'); }); it('should set correct native params', function () { const bid = [ @@ -678,5 +947,96 @@ describe('Adf adapter', function () { const result = spec.interpretResponse(serverResponse, bidRequest)[0]; assert.ok(!result); }); + + describe('banner', function () { + it('should set ad content on response', function () { + let serverResponse = { + body: { + seatbid: [{ + bid: [{ impid: '1', adm: '', ext: { prebid: { type: 'banner' } } }] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { mid: 1000 } + } + ] + }; + + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(bids.length, 1); + assert.equal(bids[0].ad, ''); + assert.equal(bids[0].mediaType, 'banner'); + assert.equal(bids[0].meta.mediaType, 'banner'); + }); + }); + + describe('video', function () { + it('should set vastXml on response', function () { + let serverResponse = { + body: { + seatbid: [{ + bid: [{ impid: '1', adm: '', ext: { prebid: { type: 'video' } } }] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { mid: 1000 } + } + ] + }; + + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(bids.length, 1); + assert.equal(bids[0].vastXml, ''); + assert.equal(bids[0].mediaType, 'video'); + assert.equal(bids[0].meta.mediaType, 'video'); + }); + + it('should add renderer for outstream bids', function () { + let serverResponse = { + body: { + seatbid: [{ + bid: [{ impid: '1', adm: '', ext: { prebid: { type: 'video' } } }, { impid: '2', adm: '', ext: { prebid: { type: 'video' } } }] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { mid: 1000 }, + mediaTypes: { + video: { + context: 'outstream' + } + } + }, + { + bidId: 'bidId2', + params: { mid: 1000 }, + mediaTypes: { + video: { + constext: 'instream' + } + } + } + ] + }; + + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.ok(bids[0].renderer); + assert.equal(bids[1].renderer, undefined); + }); + }); }); }); diff --git a/test/spec/modules/adfinityBidAdapter_spec.js b/test/spec/modules/adfinityBidAdapter_spec.js deleted file mode 100644 index 479a2303dd5..00000000000 --- a/test/spec/modules/adfinityBidAdapter_spec.js +++ /dev/null @@ -1,151 +0,0 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/adfinityBidAdapter.js'; - -describe('AdfinityAdapter', function () { - let bid = { - bidId: '2dd581a2b6281d', - bidder: 'adfinity', - bidderRequestId: '145e1d6a7837c9', - params: { - placement_id: 0 - }, - placementCode: 'placementid_0', - auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', - schain: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'example.com', - sid: '0', - hp: 1, - rid: 'bidrequestid', - domain: 'example.com' - } - ] - } - }; - let bidderRequest = { - bidderCode: 'adfinity', - auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', - bidderRequestId: 'ffffffffffffff', - start: 1472239426002, - auctionStart: 1472239426000, - timeout: 5000, - uspConsent: '1YN-', - refererInfo: { - referer: 'http://www.example.com', - reachedTop: true, - }, - bids: [bid] - } - - describe('isBidRequestValid', function () { - it('Should return true when placement_id can be cast to a number', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; - }); - it('Should return false when placement_id is not a number', function () { - bid.params.placement_id = 'aaa'; - expect(spec.isBidRequestValid(bid)).to.be.false; - }); - }); - - describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); - it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequest).to.exist; - expect(serverRequest.method).to.exist; - expect(serverRequest.url).to.exist; - expect(serverRequest.data).to.exist; - }); - it('Returns POST method', function () { - expect(serverRequest.method).to.equal('POST'); - }); - it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://stat.adfinity.pro/?c=o&m=multi'); - }); - - it('Returns valid data if array of bids is valid', function () { - let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.language).to.be.a('string'); - expect(data.secure).to.be.within(0, 1); - expect(data.host).to.be.a('string'); - expect(data.page).to.be.a('string'); - let placements = data['placements']; - for (let i = 0; i < placements.length; i++) { - let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain'); - expect(placement.schain).to.be.an('object') - expect(placement.placementId).to.be.a('number'); - expect(placement.bidId).to.be.a('string'); - expect(placement.traffic).to.be.a('string'); - expect(placement.sizes).to.be.an('array'); - } - }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); - }); - describe('interpretResponse', function () { - let resObject = { - body: [ { - requestId: '123', - mediaType: 'banner', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD' - } ] - }; - let serverResponses = spec.interpretResponse(resObject); - it('Returns an array of valid server responses if response object is valid', function () { - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - let dataItem = serverResponses[i]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - expect(dataItem.ad).to.be.a('string'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.mediaType).to.be.a('string'); - } - it('Returns an empty array if invalid response is passed', function () { - serverResponses = spec.interpretResponse('invalid_response'); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - }); - }); - - describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs(); - it('Returns valid URL and type', function () { - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.exist; - expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://stat.adfinity.pro/?c=o&m=cookie'); - }); - }); -}); diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js deleted file mode 100644 index 79ea76da8dd..00000000000 --- a/test/spec/modules/adformBidAdapter_spec.js +++ /dev/null @@ -1,550 +0,0 @@ -import {assert, expect} from 'chai'; -import {spec} from 'modules/adformBidAdapter.js'; -import { BANNER, VIDEO } from 'src/mediaTypes.js'; -import { config } from 'src/config.js'; -import { createEidsArray } from 'modules/userId/eids.js'; - -describe('Adform adapter', function () { - let serverResponse, bidRequest, bidResponses; - let bids = []; - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'adform', - 'params': { - 'mid': '19910113' - } - }; - - it('should return true when required params found', function () { - assert(spec.isBidRequestValid(bid)); - }); - - it('should return false when required params are missing', function () { - bid.params = { - adxDomain: 'adx.adform.net' - }; - assert.isFalse(spec.isBidRequestValid(bid)); - }); - }); - - describe('buildRequests', function () { - it('should pass multiple bids via single request', function () { - let request = spec.buildRequests(bids); - let parsedUrl = parseUrl(request.url); - assert.lengthOf(parsedUrl.items, 7); - }); - - it('should handle global request parameters', function () { - let parsedUrl = parseUrl(spec.buildRequests([bids[0]]).url); - let query = parsedUrl.query; - - assert.equal(parsedUrl.path, 'https://newDomain/adx'); - assert.equal(query.tid, 45); - assert.equal(query.rp, 4); - assert.equal(query.fd, 1); - assert.equal(query.stid, '7aefb970-2045'); - assert.equal(query.url, encodeURIComponent('some// there')); - }); - - it('should set correct request method', function () { - let request = spec.buildRequests([bids[0]]); - assert.equal(request.method, 'GET'); - }); - - it('should pass request currency from config', function () { - config.setConfig({ currency: { adServerCurrency: 'PLN' } }); - let request = parseUrl(spec.buildRequests(bids).url); - - request.items.forEach(item => { - assert.equal(item.rcur, 'PLN'); - }); - }); - - it('should prefer bid currency over global config', function () { - config.setConfig({ currency: { adServerCurrency: 'PLN' } }); - bids[0].params.rcur = 'USD'; - let request = parseUrl(spec.buildRequests(bids).url); - const currencies = request.items.map(item => item.rcur); - - assert.deepEqual(currencies, [ 'USD', 'PLN', 'PLN', 'PLN', 'PLN', 'PLN', 'PLN' ]); - }); - - it('should correctly form bid items', function () { - let bidList = bids; - let request = spec.buildRequests(bidList); - let parsedUrl = parseUrl(request.url); - assert.deepEqual(parsedUrl.items, [ - { - mid: '1', - transactionId: '5f33781f-9552-4ca1' - }, - { - mid: '2', - someVar: 'someValue', - pt: 'gross', - transactionId: '5f33781f-9552-4iuy' - }, - { - mid: '3', - pdom: 'home', - transactionId: '5f33781f-9552-7ev3' - }, - { - mid: '3', - pdom: 'home', - transactionId: '5f33781f-9552-7ev3' - }, - { - mid: '3', - pdom: 'home', - transactionId: '5f33781f-9552-7ev3' - }, - { - mid: '5', - pt: 'net', - transactionId: '5f33781f-9552-7ev3', - }, - { - mid: '6', - pt: 'gross', - transactionId: '5f33781f-9552-7ev3' - } - ]); - }); - - it('should not change original validBidRequests object', function () { - var resultBids = JSON.parse(JSON.stringify(bids[0])); - let request = spec.buildRequests([bids[0]]); - assert.deepEqual(resultBids, bids[0]); - }); - - it('should set gross to the request, if there is any gross priceType', function () { - let request = spec.buildRequests([bids[5], bids[5]]); - let parsedUrl = parseUrl(request.url); - - assert.equal(parsedUrl.query.pt, 'net'); - - request = spec.buildRequests([bids[4], bids[3]]); - parsedUrl = parseUrl(request.url); - - assert.equal(parsedUrl.query.pt, 'gross'); - }); - - it('should pass extended ids', function () { - bids[0].userIdAsEids = createEidsArray({ - tdid: 'TTD_ID_FROM_USER_ID_MODULE', - pubcid: 'pubCommonId_FROM_USER_ID_MODULE' - }); - let request = spec.buildRequests(bids); - let eids = parseUrl(request.url).query.eids; - - assert.equal(eids, 'eyJhZHNlcnZlci5vcmciOnsiVFREX0lEX0ZST01fVVNFUl9JRF9NT0RVTEUiOlsxXX0sInB1YmNpZC5vcmciOnsicHViQ29tbW9uSWRfRlJPTV9VU0VSX0lEX01PRFVMRSI6WzFdfX0%3D'); - assert.deepEqual(JSON.parse(atob(decodeURIComponent(eids))), { - 'adserver.org': { - 'TTD_ID_FROM_USER_ID_MODULE': [1] - }, - 'pubcid.org': { - 'pubCommonId_FROM_USER_ID_MODULE': [1] - } - }); - }); - - it('should allow to pass custom extended ids', function () { - bids[0].params.eids = 'some_id_value'; - let request = spec.buildRequests(bids); - let eids = parseUrl(request.url).query.eids; - - assert.equal(eids, 'some_id_value'); - }); - - it('should add parameter to global parameters if it exists in all bids', function () { - const _bids = []; - _bids.push(bids[0]); - _bids.push(bids[1]); - _bids.push(bids[2]); - _bids[0].params.mkv = 'key:value,key1:value1'; - _bids[1].params.mkv = 'key:value,key1:value1,keyR:removed'; - _bids[2].params.mkv = 'key:value,key1:value1,keyR:removed,key8:value1'; - _bids[0].params.mkw = 'targeting'; - _bids[1].params.mkw = 'targeting'; - _bids[2].params.mkw = 'targeting,targeting2'; - _bids[0].params.msw = 'search:word,search:word2'; - _bids[1].params.msw = 'search:word'; - _bids[2].params.msw = 'search:word,search:word5'; - let bidList = _bids; - let request = spec.buildRequests(bidList); - let parsedUrl = parseUrl(request.url); - assert.equal(parsedUrl.query.mkv, encodeURIComponent('key:value,key1:value1')); - assert.equal(parsedUrl.query.mkw, 'targeting'); - assert.equal(parsedUrl.query.msw, encodeURIComponent('search:word')); - assert.ok(!parsedUrl.items[0].mkv); - assert.ok(!parsedUrl.items[0].mkw); - assert.equal(parsedUrl.items[0].msw, 'search:word2'); - assert.equal(parsedUrl.items[1].mkv, 'keyR:removed'); - assert.ok(!parsedUrl.items[1].mkw); - assert.ok(!parsedUrl.items[1].msw); - assert.equal(parsedUrl.items[2].mkv, 'keyR:removed,key8:value1'); - assert.equal(parsedUrl.items[2].mkw, 'targeting2'); - assert.equal(parsedUrl.items[2].msw, 'search:word5'); - }); - - describe('user privacy', function () { - it('should send GDPR Consent data to adform if gdprApplies', function () { - let request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: true, consentString: 'concentDataString'}}); - let parsedUrl = parseUrl(request.url).query; - - assert.equal(parsedUrl.gdpr, '1'); - assert.equal(parsedUrl.gdpr_consent, 'concentDataString'); - }); - - it('should not send GDPR Consent data to adform if gdprApplies is undefined', function () { - let request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: false, consentString: 'concentDataString'}}); - let parsedUrl = parseUrl(request.url).query; - - assert.equal(parsedUrl.gdpr, '0'); - assert.equal(parsedUrl.gdpr_consent, 'concentDataString'); - - request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: undefined, consentString: 'concentDataString'}}); - parsedUrl = parseUrl(request.url).query; - assert.ok(!parsedUrl.gdpr); - assert.ok(!parsedUrl.gdpr_consent); - }); - - it('should return GDPR Consent data with request data', function () { - let request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: true, consentString: 'concentDataString'}}); - - assert.deepEqual(request.gdpr, { - gdpr: true, - gdpr_consent: 'concentDataString' - }); - - request = spec.buildRequests([bids[0]]); - assert.ok(!request.gdpr); - }); - - it('should send CCPA Consent data to adform', function () { - const request = spec.buildRequests([bids[0]], {uspConsent: '1YA-'}); - const parsedUrl = parseUrl(request.url).query; - - assert.equal(parsedUrl.us_privacy, '1YA-'); - }); - }); - }); - - describe('interpretResponse', function () { - it('should respond with empty response when there is empty serverResponse', function () { - let result = spec.interpretResponse({ body: {} }, {}); - assert.deepEqual(result, []); - }); - it('should respond with empty response when response from server is not banner', function () { - serverResponse.body[0].response = 'not banner'; - serverResponse.body = [serverResponse.body[0]]; - bidRequest.bids = [bidRequest.bids[0]]; - let result = spec.interpretResponse(serverResponse, bidRequest); - - assert.deepEqual(result, []); - }); - it('should interpret server response correctly with one bid', function () { - serverResponse.body = [serverResponse.body[0]]; - bidRequest.bids = [bidRequest.bids[0]]; - let result = spec.interpretResponse(serverResponse, bidRequest)[0]; - - assert.equal(result.requestId, '2a0cf4e'); - assert.equal(result.cpm, 13.9); - assert.equal(result.width, 300); - assert.equal(result.height, 250); - assert.equal(result.creativeId, '2a0cf4e'); - assert.equal(result.dealId, '123abc'); - assert.equal(result.currency, 'EUR'); - assert.equal(result.netRevenue, true); - assert.equal(result.ttl, 360); - assert.deepEqual(result.meta.advertiserDomains, []) - assert.equal(result.ad, ''); - assert.equal(result.bidderCode, 'adform'); - assert.equal(result.transactionId, '5f33781f-9552-4ca1'); - }); - - it('should set correct netRevenue', function () { - serverResponse.body = [serverResponse.body[0]]; - bidRequest.bids = [bidRequest.bids[1]]; - bidRequest.netRevenue = 'gross'; - let result = spec.interpretResponse(serverResponse, bidRequest)[0]; - - assert.equal(result.netRevenue, false); - }); - - it('should create bid response item for every requested item', function () { - let result = spec.interpretResponse(serverResponse, bidRequest); - assert.lengthOf(result, 5); - }); - - it('should create bid response with vast xml', function () { - const result = spec.interpretResponse(serverResponse, bidRequest)[3]; - assert.equal(result.vastXml, ''); - }); - - it('should create bid response with vast url', function () { - const result = spec.interpretResponse(serverResponse, bidRequest)[4]; - assert.equal(result.vastUrl, 'vast://url'); - }); - - it('should set mediaType on bid response', function () { - const expected = [ BANNER, BANNER, BANNER, VIDEO, VIDEO ]; - const result = spec.interpretResponse(serverResponse, bidRequest); - for (let i = 0; i < result.length; i++) { - assert.equal(result[i].mediaType, expected[i]); - } - }); - - it('should set default netRevenue as gross', function () { - bidRequest.netRevenue = 'gross'; - const result = spec.interpretResponse(serverResponse, bidRequest); - for (let i = 0; i < result.length; i++) { - assert.equal(result[i].netRevenue, false); - } - }); - - it('should set gdpr if it exist in bidRequest', function () { - bidRequest.gdpr = { - gdpr: true, - gdpr_consent: 'ERW342EIOWT34234KMGds' - }; - let result = spec.interpretResponse(serverResponse, bidRequest); - for (let i = 0; i < result.length; i++) { - assert.equal(result[i].gdpr, true); - assert.equal(result[i].gdpr_consent, 'ERW342EIOWT34234KMGds'); - } - - bidRequest.gdpr = undefined; - result = spec.interpretResponse(serverResponse, bidRequest); - for (let i = 0; i < result.length; i++) { - assert.ok(!result[i].gdpr); - assert.ok(!result[i].gdpr_consent); - } - }); - - it('should set a renderer only for an outstream context', function () { - serverResponse.body = [serverResponse.body[3], serverResponse.body[2]]; - bidRequest.bids = [bidRequest.bids[6], bidRequest.bids[6]]; - let result = spec.interpretResponse(serverResponse, bidRequest); - assert.ok(result[0].renderer); - assert.equal(result[1].renderer, undefined); - }); - - describe('verifySizes', function () { - it('should respond with empty response when sizes doesn\'t match', function () { - serverResponse.body[0].response = 'banner'; - serverResponse.body[0].width = 100; - serverResponse.body[0].height = 150; - - serverResponse.body = [serverResponse.body[0]]; - bidRequest.bids = [bidRequest.bids[0]]; - let result = spec.interpretResponse(serverResponse, bidRequest); - - assert.equal(serverResponse.body.length, 1); - assert.equal(serverResponse.body[0].response, 'banner'); - assert.deepEqual(result, []); - }); - it('should respond with empty response when sizes as a strings doesn\'t match', function () { - serverResponse.body[0].response = 'banner'; - serverResponse.body[0].width = 100; - serverResponse.body[0].height = 150; - - serverResponse.body = [serverResponse.body[0]]; - bidRequest.bids = [bidRequest.bids[0]]; - - bidRequest.bids[0].sizes = [['101', '150']]; - let result = spec.interpretResponse(serverResponse, bidRequest); - - assert.equal(serverResponse.body.length, 1); - assert.equal(serverResponse.body[0].response, 'banner'); - assert.deepEqual(result, []); - }); - it('should support size dimensions as a strings', function () { - serverResponse.body[0].response = 'banner'; - serverResponse.body[0].width = 300; - serverResponse.body[0].height = 600; - - serverResponse.body = [serverResponse.body[0]]; - bidRequest.bids = [bidRequest.bids[0]]; - - bidRequest.bids[0].sizes = [['300', '250'], ['250', '300'], ['300', '600'], ['600', '300']]; - let result = spec.interpretResponse(serverResponse, bidRequest); - - assert.equal(result[0].width, 300); - assert.equal(result[0].height, 600); - }); - }); - }); - - beforeEach(function () { - config.setConfig({ currency: {} }); - - let sizes = [[250, 300], [300, 250], [300, 600], [600, 300]]; - let placementCode = ['div-01', 'div-02', 'div-03', 'div-04', 'div-05']; - let mediaTypes = [{video: {context: 'outstream'}, banner: {sizes: sizes[3]}}]; - let params = [{ mid: 1, url: 'some// there' }, {adxDomain: null, mid: 2, someVar: 'someValue', pt: 'gross'}, { adxDomain: null, mid: 3, pdom: 'home' }, {mid: 5, pt: 'net'}, {mid: 6, pt: 'gross'}]; - bids = [ - { - adUnitCode: placementCode[0], - auctionId: '7aefb970-2045', - bidId: '2a0cf4e', - bidder: 'adform', - bidderRequestId: '1ab8d9', - params: params[0], - adxDomain: 'newDomain', - tid: 45, - placementCode: placementCode[0], - sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], - transactionId: '5f33781f-9552-4ca1' - }, - { - adUnitCode: placementCode[1], - auctionId: '7aefb970-2045', - bidId: '2a0cf5b', - bidder: 'adform', - bidderRequestId: '1ab8d9', - params: params[1], - placementCode: placementCode[1], - sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], - transactionId: '5f33781f-9552-4iuy' - }, - { - adUnitCode: placementCode[2], - auctionId: '7aefb970-2045', - bidId: '2a0cf6n', - bidder: 'adform', - bidderRequestId: '1ab8d9', - params: params[2], - placementCode: placementCode[2], - sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], - transactionId: '5f33781f-9552-7ev3' - }, - { - adUnitCode: placementCode[3], - auctionId: '7aefb970-2045', - bidId: '2a0cf6n', - bidder: 'adform', - bidderRequestId: '1ab8d9', - params: params[2], - placementCode: placementCode[2], - sizes: [], - transactionId: '5f33781f-9552-7ev3' - }, - { - adUnitCode: placementCode[4], - auctionId: '7aefb970-2045', - bidId: '2a0cf6n', - bidder: 'adform', - bidderRequestId: '1ab8d9', - params: params[2], - placementCode: placementCode[2], - sizes: [], - transactionId: '5f33781f-9552-7ev3' - }, - { - adUnitCode: placementCode[4], - auctionId: '7aefb970-2045', - bidId: '2a0cf6n', - bidder: 'adform', - bidderRequestId: '1ab8d9', - params: params[3], - placementCode: placementCode[2], - sizes: [], - transactionId: '5f33781f-9552-7ev3' - }, - { - adUnitCode: placementCode[4], - auctionId: '7aefb970-2045', - bidId: '2a0cf6n', - bidder: 'adform', - bidderRequestId: '1ab8d9', - params: params[4], - placementCode: placementCode[2], - sizes: [], - mediaTypes: mediaTypes[0], - transactionId: '5f33781f-9552-7ev3' - } - ]; - serverResponse = { - body: [ - { - banner: '', - deal_id: '123abc', - height: 250, - response: 'banner', - width: 300, - win_bid: 13.9, - win_cur: 'EUR' - }, - { - banner: '', - deal_id: '123abc', - height: 300, - response: 'banner', - width: 250, - win_bid: 13.9, - win_cur: 'EUR' - }, - { - banner: '', - deal_id: '123abc', - height: 300, - response: 'banner', - width: 600, - win_bid: 10, - win_cur: 'EUR' - }, - { - deal_id: '123abc', - height: 300, - response: 'vast_content', - width: 600, - win_bid: 10, - win_cur: 'EUR', - vast_content: '' - }, - { - deal_id: '123abc', - height: 300, - response: 'vast_url', - width: 600, - win_bid: 10, - win_cur: 'EUR', - vast_url: 'vast://url' - } - ], - headers: {} - }; - bidRequest = { - bidder: 'adform', - bids: bids, - method: 'GET', - url: 'url', - netRevenue: 'net' - }; - }); -}); - -function parseUrl(url) { - const parts = url.split('/'); - const query = parts.pop().split('&'); - return { - path: parts.join('/'), - items: query - .filter((i) => !~i.indexOf('=')) - .map((i) => atob(decodeURIComponent(i)) - .split('&') - .reduce(toObject, {})), - query: query - .filter((i) => ~i.indexOf('=')) - .map((i) => i.replace('?', '')) - .reduce(toObject, {}) - }; -} - -function toObject(cache, string) { - const keyValue = string.split('='); - cache[keyValue[0]] = keyValue[1]; - return cache; -} diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js index 927e7910723..3ff5e1fb302 100644 --- a/test/spec/modules/adgenerationBidAdapter_spec.js +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -92,9 +92,9 @@ describe('AdgenerationAdapter', function () { } }; const data = { - banner: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.0.1&imark=1&tp=https%3A%2F%2Fexample.com`, - bannerUSD: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=USD&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.0.1&imark=1&tp=https%3A%2F%2Fexample.com`, - native: 'posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=1x1¤cy=JPY&pbver=' + prebid.version + '&sdkname=prebidjs&adapterver=1.0.1&tp=https%3A%2F%2Fexample.com' + banner: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.2.0&imark=1&tp=https%3A%2F%2Fexample.com`, + bannerUSD: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=USD&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.2.0&imark=1&tp=https%3A%2F%2Fexample.com`, + native: 'posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=1x1¤cy=JPY&pbver=' + prebid.version + '&sdkname=prebidjs&adapterver=1.2.0&tp=https%3A%2F%2Fexample.com' }; it('sends bid request to ENDPOINT via GET', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[0]; @@ -192,171 +192,603 @@ describe('AdgenerationAdapter', function () { transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' }, }, + upperBillboard: { + bidRequest: { + bidder: 'adg', + params: { + id: '143038', // banner + marginTop: '50', + }, + adUnitCode: 'adunit-code', + sizes: [[320, 180]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + }, + }, }; const serverResponse = { noAd: { results: [], }, - banner: { - ad: '
', - beacon: '', - cpm: 36.0008, - displaytype: '1', - ids: {}, - w: 320, - h: 100, - location_params: null, - locationid: '58279', - rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000, - results: [ - {ad: '
'}, - ] - }, - native: { - ad: '↵ ↵ ↵ ↵ ↵
↵ ', - beacon: '', - cpm: 36.0008, - displaytype: '1', - ids: {}, - location_params: null, - locationid: '58279', - native_ad: { - assets: [ - { - data: { - label: 'accompanying_text', - value: 'AD' + normal: { + banner: { + ad: '
', + beacon: '', + cpm: 36.0008, + displaytype: '1', + ids: {}, + w: 320, + h: 100, + location_params: null, + locationid: '58279', + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000, + results: [ + {ad: '<\!DOCTYPE html>
'}, + ], + adomain: ['advertiserdomain.com'] + }, + native: { + ad: '<\!DOCTYPE html>↵ ↵ ↵ ↵ ↵
↵ ', + beacon: '', + cpm: 36.0008, + displaytype: '1', + ids: {}, + location_params: null, + locationid: '58279', + adomain: ['advertiserdomain.com'], + native_ad: { + assets: [ + { + data: { + label: 'accompanying_text', + value: 'AD' + }, + id: 501 }, - id: 501 - }, - { - data: { - label: 'optout_url', - value: 'https://supership.jp/optout/#' + { + data: { + label: 'optout_url', + value: 'https://supership.jp/optout/#' + }, + id: 502 }, - id: 502 - }, - { - data: { - ext: { - black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', + { + data: { + ext: { + black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', + }, + label: 'information_icon_url', + value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', + id: 503 + } + }, + { + id: 1, + required: 1, + title: {text: 'Title'} + }, + { + id: 2, + img: { + h: 250, + url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', + w: 300 }, - label: 'information_icon_url', - value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', - id: 503 - } - }, - { - id: 1, - required: 1, - title: {text: 'Title'} - }, - { - id: 2, - img: { - h: 250, - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - w: 300 + required: 1 }, - required: 1 - }, - { - id: 3, - img: { - h: 300, - url: 'https://placehold.jp/300x300.png', - w: 300 + { + id: 3, + img: { + h: 300, + url: 'https://placehold.jp/300x300.png', + w: 300 + }, + required: 1 }, - required: 1 - }, - { - data: {value: 'Description'}, - id: 5, - required: 0 - }, - { - data: {value: 'CTA'}, - id: 6, - required: 0 + { + data: {value: 'Description'}, + id: 5, + required: 0 + }, + { + data: {value: 'CTA'}, + id: 6, + required: 0 + }, + { + data: {value: 'Sponsored'}, + id: 4, + required: 0 + } + ], + imptrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'], + link: { + clicktrackers: [ + 'https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif' + ], + url: 'https://supership.jp' }, + }, + results: [ + {ad: 'Creative<\/body>'} + ], + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000 + }, + upperBillboard: { + 'ad': '<\!DOCTYPE html>\n \n \n \n \n \n \n \n
\n \n
\n \n', + 'beacon': '', + 'beaconurl': 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif', + 'cpm': 80, + 'creative_params': {}, + 'creativeid': 'ScaleOut_2146187', + 'dealid': '2134-132864_newformat_test', + 'displaytype': '1', + 'h': 180, + 'ids': { + 'anid': '', + 'diid': '', + 'idfa': '', + 'soc': 'Xm8Q8cCo5r8AAHCCMg0AAAAA' + }, + 'location_params': { + 'option': { + 'ad_type': 'upper_billboard' + } + }, + 'locationid': '143038', + 'results': [ { - data: {value: 'Sponsored'}, - id: 4, - required: 0 + 'ad': '<\!DOCTYPE html>\n \n \n \n \n \n \n \n
\n \n
\n \n', + 'beacon': '', + 'beaconurl': 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif', + 'cpm': 80, + 'creative_params': {}, + 'creativeid': 'ScaleOut_2146187', + 'dealid': '2134-132864_newformat_test', + 'h': 180, + 'landing_url': 'https://supership.jp/', + 'rparams': {}, + 'scheduleid': '1233323', + 'trackers': { + 'imp': [ + 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif' + ], + 'viewable_imp': [ + 'https://tg.socdm.com/aux/inview?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' + ], + 'viewable_measured': [ + 'https://tg.socdm.com/aux/measured?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' + ] + }, + 'ttl': 1000, + 'vastxml': '\n \n \n SOADS\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n https://i.socdm.com/a/2/2095/2091787/20210810043037-de3e74aec30f36.mp4\n https://i.socdm.com/a/2/2095/2091788/20210810043037-6dd368dc91d507.mp4\n https://i.socdm.com/a/2/2095/2091789/20210810043037-c8eb814ddd85c4.webm\n https://i.socdm.com/a/2/2095/2091790/20210810043037-0a7f74c40268ab.webm\n \n \n \n \n \n \n', + 'vcpm': 0, + 'w': 320, + 'weight': 1 } ], - imptrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'], - link: { - clicktrackers: [ - 'https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif' + 'rotation': '0', + 'scheduleid': '1233323', + 'sdktype': '0', + 'trackers': { + 'imp': [ + 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif' + ], + 'viewable_imp': [ + 'https://tg.socdm.com/aux/inview?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' + ], + 'viewable_measured': [ + 'https://tg.socdm.com/aux/measured?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' + ] + }, + 'ttl': 1000, + 'vastxml': '\n \n \n SOADS\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n https://i.socdm.com/a/2/2095/2091787/20210810043037-de3e74aec30f36.mp4\n https://i.socdm.com/a/2/2095/2091788/20210810043037-6dd368dc91d507.mp4\n https://i.socdm.com/a/2/2095/2091789/20210810043037-c8eb814ddd85c4.webm\n https://i.socdm.com/a/2/2095/2091790/20210810043037-0a7f74c40268ab.webm\n \n \n \n \n \n \n', + 'vcpm': 0, + 'w': 320, + } + }, + emptyAdomain: { + banner: { + ad: '
', + beacon: '', + cpm: 36.0008, + displaytype: '1', + ids: {}, + w: 320, + h: 100, + location_params: null, + locationid: '58279', + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000, + results: [ + {ad: '<\!DOCTYPE html>
'}, + ], + adomain: [] + }, + native: { + ad: '<\!DOCTYPE html>↵ ↵ ↵ ↵ ↵
↵ ', + beacon: '', + cpm: 36.0008, + displaytype: '1', + ids: {}, + location_params: null, + locationid: '58279', + adomain: [], + native_ad: { + assets: [ + { + data: { + label: 'accompanying_text', + value: 'AD' + }, + id: 501 + }, + { + data: { + label: 'optout_url', + value: 'https://supership.jp/optout/#' + }, + id: 502 + }, + { + data: { + ext: { + black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', + }, + label: 'information_icon_url', + value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', + id: 503 + } + }, + { + id: 1, + required: 1, + title: {text: 'Title'} + }, + { + id: 2, + img: { + h: 250, + url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', + w: 300 + }, + required: 1 + }, + { + id: 3, + img: { + h: 300, + url: 'https://placehold.jp/300x300.png', + w: 300 + }, + required: 1 + }, + { + data: {value: 'Description'}, + id: 5, + required: 0 + }, + { + data: {value: 'CTA'}, + id: 6, + required: 0 + }, + { + data: {value: 'Sponsored'}, + id: 4, + required: 0 + } ], - url: 'https://supership.jp' + imptrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'], + link: { + clicktrackers: [ + 'https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif' + ], + url: 'https://supership.jp' + }, }, + results: [ + {ad: 'Creative<\/body>'} + ], + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000 + } + }, + noAdomain: { + banner: { + ad: '
', + beacon: '', + cpm: 36.0008, + displaytype: '1', + ids: {}, + w: 320, + h: 100, + location_params: null, + locationid: '58279', + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000, + results: [ + {ad: '<\!DOCTYPE html>
'}, + ], }, - results: [ - {ad: 'Creative<\/body>'} - ], - rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000 + native: { + ad: '<\!DOCTYPE html>↵ ↵ ↵ ↵ ↵
↵ ', + beacon: '', + cpm: 36.0008, + displaytype: '1', + ids: {}, + location_params: null, + locationid: '58279', + native_ad: { + assets: [ + { + data: { + label: 'accompanying_text', + value: 'AD' + }, + id: 501 + }, + { + data: { + label: 'optout_url', + value: 'https://supership.jp/optout/#' + }, + id: 502 + }, + { + data: { + ext: { + black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', + }, + label: 'information_icon_url', + value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', + id: 503 + } + }, + { + id: 1, + required: 1, + title: {text: 'Title'} + }, + { + id: 2, + img: { + h: 250, + url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', + w: 300 + }, + required: 1 + }, + { + id: 3, + img: { + h: 300, + url: 'https://placehold.jp/300x300.png', + w: 300 + }, + required: 1 + }, + { + data: {value: 'Description'}, + id: 5, + required: 0 + }, + { + data: {value: 'CTA'}, + id: 6, + required: 0 + }, + { + data: {value: 'Sponsored'}, + id: 4, + required: 0 + } + ], + imptrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'], + link: { + clicktrackers: [ + 'https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif' + ], + url: 'https://supership.jp' + }, + }, + results: [ + {ad: 'Creative<\/body>'} + ], + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000 + } } }; const bidResponses = { - banner: { - requestId: '2f6ac468a9c15e', - cpm: 36.0008, - width: 320, - height: 100, - creativeId: '1k2kv35vsa5r', - dealId: 'fd5sa5fa7f', - currency: 'JPY', - netRevenue: true, - ttl: 1000, - ad: '
', - }, - native: { - requestId: '2f6ac468a9c15e', - cpm: 36.0008, - width: 1, - height: 1, - creativeId: '1k2kv35vsa5r', - dealId: 'fd5sa5fa7f', - currency: 'JPY', - netRevenue: true, - ttl: 1000, - ad: '↵
', + normal: { + banner: { + requestId: '2f6ac468a9c15e', + cpm: 36.0008, + width: 320, + height: 100, + creativeId: '1k2kv35vsa5r', + dealId: 'fd5sa5fa7f', + currency: 'JPY', + netRevenue: true, + ttl: 1000, + ad: '
', + adomain: ['advertiserdomain.com'] + }, native: { - title: 'Title', - image: { - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - height: 250, - width: 300 + requestId: '2f6ac468a9c15e', + cpm: 36.0008, + width: 1, + height: 1, + creativeId: '1k2kv35vsa5r', + dealId: 'fd5sa5fa7f', + currency: 'JPY', + netRevenue: true, + ttl: 1000, + adomain: ['advertiserdomain.com'], + ad: '↵
', + native: { + title: 'Title', + image: { + url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', + height: 250, + width: 300 + }, + icon: { + url: 'https://placehold.jp/300x300.png', + height: 300, + width: 300 + }, + sponsoredBy: 'Sponsored', + body: 'Description', + cta: 'CTA', + privacyLink: 'https://supership.jp/optout/#', + clickUrl: 'https://supership.jp', + clickTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif'], + impressionTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'] }, - icon: { - url: 'https://placehold.jp/300x300.png', - height: 300, - width: 300 + mediaType: NATIVE + }, + upperBillboard: { + requestId: '2f6ac468a9c15e', + cpm: 80, + width: 320, + height: 180, + creativeId: 'ScaleOut_2146187', + dealId: '2134-132864_newformat_test', + currency: 'JPY', + netRevenue: true, + ttl: 1000, + ad: ``, + adomain: ['advertiserdomain.com'] + }, + }, + emptyAdomain: { + banner: { + requestId: '2f6ac468a9c15e', + cpm: 36.0008, + width: 320, + height: 100, + creativeId: '1k2kv35vsa5r', + dealId: 'fd5sa5fa7f', + currency: 'JPY', + netRevenue: true, + ttl: 1000, + ad: '
', + adomain: [] + }, + native: { + requestId: '2f6ac468a9c15e', + cpm: 36.0008, + width: 1, + height: 1, + creativeId: '1k2kv35vsa5r', + dealId: 'fd5sa5fa7f', + currency: 'JPY', + netRevenue: true, + ttl: 1000, + adomain: [], + ad: '↵
', + native: { + title: 'Title', + image: { + url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', + height: 250, + width: 300 + }, + icon: { + url: 'https://placehold.jp/300x300.png', + height: 300, + width: 300 + }, + sponsoredBy: 'Sponsored', + body: 'Description', + cta: 'CTA', + privacyLink: 'https://supership.jp/optout/#', + clickUrl: 'https://supership.jp', + clickTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif'], + impressionTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'] }, - sponsoredBy: 'Sponsored', - body: 'Description', - cta: 'CTA', - privacyLink: 'https://supership.jp/optout/#', - clickUrl: 'https://supership.jp', - clickTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif'], - impressionTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'] + mediaType: NATIVE }, - mediaType: NATIVE - } + }, + noAdomain: { + banner: { + requestId: '2f6ac468a9c15e', + cpm: 36.0008, + width: 320, + height: 100, + creativeId: '1k2kv35vsa5r', + dealId: 'fd5sa5fa7f', + currency: 'JPY', + netRevenue: true, + ttl: 1000, + ad: '
', + }, + native: { + requestId: '2f6ac468a9c15e', + cpm: 36.0008, + width: 1, + height: 1, + creativeId: '1k2kv35vsa5r', + dealId: 'fd5sa5fa7f', + currency: 'JPY', + netRevenue: true, + ttl: 1000, + ad: '↵
', + native: { + title: 'Title', + image: { + url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', + height: 250, + width: 300 + }, + icon: { + url: 'https://placehold.jp/300x300.png', + height: 300, + width: 300 + }, + sponsoredBy: 'Sponsored', + body: 'Description', + cta: 'CTA', + privacyLink: 'https://supership.jp/optout/#', + clickUrl: 'https://supership.jp', + clickTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif'], + impressionTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'] + }, + mediaType: NATIVE + } + }, }; it('no bid responses', function () { @@ -364,44 +796,105 @@ describe('AdgenerationAdapter', function () { expect(result.length).to.equal(0); }); - it('handles banner responses', function () { - const result = spec.interpretResponse({body: serverResponse.banner}, bidRequests.banner)[0]; - expect(result.requestId).to.equal(bidResponses.banner.requestId); - expect(result.width).to.equal(bidResponses.banner.width); - expect(result.height).to.equal(bidResponses.banner.height); - expect(result.creativeId).to.equal(bidResponses.banner.creativeId); - expect(result.dealId).to.equal(bidResponses.banner.dealId); - expect(result.currency).to.equal(bidResponses.banner.currency); - expect(result.netRevenue).to.equal(bidResponses.banner.netRevenue); - expect(result.ttl).to.equal(bidResponses.banner.ttl); - expect(result.ad).to.equal(bidResponses.banner.ad); + it('handles ADGBrowserM responses', function () { + const result = spec.interpretResponse({body: serverResponse.normal.upperBillboard}, bidRequests.upperBillboard)[0]; + expect(result.requestId).to.equal(bidResponses.normal.upperBillboard.requestId); + expect(result.width).to.equal(bidResponses.normal.upperBillboard.width); + expect(result.height).to.equal(bidResponses.normal.upperBillboard.height); + expect(result.creativeId).to.equal(bidResponses.normal.upperBillboard.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.upperBillboard.dealId); + expect(result.currency).to.equal(bidResponses.normal.upperBillboard.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.upperBillboard.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.upperBillboard.ttl); + expect(result.ad).to.equal(bidResponses.normal.upperBillboard.ad); + }); + + it('handles banner responses for empty adomain', function () { + const result = spec.interpretResponse({body: serverResponse.emptyAdomain.banner}, bidRequests.banner)[0]; + expect(result.requestId).to.equal(bidResponses.emptyAdomain.banner.requestId); + expect(result.width).to.equal(bidResponses.emptyAdomain.banner.width); + expect(result.height).to.equal(bidResponses.emptyAdomain.banner.height); + expect(result.creativeId).to.equal(bidResponses.emptyAdomain.banner.creativeId); + expect(result.dealId).to.equal(bidResponses.emptyAdomain.banner.dealId); + expect(result.currency).to.equal(bidResponses.emptyAdomain.banner.currency); + expect(result.netRevenue).to.equal(bidResponses.emptyAdomain.banner.netRevenue); + expect(result.ttl).to.equal(bidResponses.emptyAdomain.banner.ttl); + expect(result.ad).to.equal(bidResponses.emptyAdomain.banner.ad); + expect(result).to.not.have.any.keys('meta'); + expect(result).to.not.have.any.keys('advertiserDomains'); + }); + + it('handles native responses for empty adomain', function () { + const result = spec.interpretResponse({body: serverResponse.emptyAdomain.native}, bidRequests.native)[0]; + expect(result.requestId).to.equal(bidResponses.emptyAdomain.native.requestId); + expect(result.width).to.equal(bidResponses.emptyAdomain.native.width); + expect(result.height).to.equal(bidResponses.emptyAdomain.native.height); + expect(result.creativeId).to.equal(bidResponses.emptyAdomain.native.creativeId); + expect(result.dealId).to.equal(bidResponses.emptyAdomain.native.dealId); + expect(result.currency).to.equal(bidResponses.emptyAdomain.native.currency); + expect(result.netRevenue).to.equal(bidResponses.emptyAdomain.native.netRevenue); + expect(result.ttl).to.equal(bidResponses.emptyAdomain.native.ttl); + expect(result.native.title).to.equal(bidResponses.emptyAdomain.native.native.title); + expect(result.native.image.url).to.equal(bidResponses.emptyAdomain.native.native.image.url); + expect(result.native.image.height).to.equal(bidResponses.emptyAdomain.native.native.image.height); + expect(result.native.image.width).to.equal(bidResponses.emptyAdomain.native.native.image.width); + expect(result.native.icon.url).to.equal(bidResponses.emptyAdomain.native.native.icon.url); + expect(result.native.icon.width).to.equal(bidResponses.emptyAdomain.native.native.icon.width); + expect(result.native.icon.height).to.equal(bidResponses.emptyAdomain.native.native.icon.height); + expect(result.native.sponsoredBy).to.equal(bidResponses.emptyAdomain.native.native.sponsoredBy); + expect(result.native.body).to.equal(bidResponses.emptyAdomain.native.native.body); + expect(result.native.cta).to.equal(bidResponses.emptyAdomain.native.native.cta); + expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.emptyAdomain.native.native.privacyLink); + expect(result.native.clickUrl).to.equal(bidResponses.emptyAdomain.native.native.clickUrl); + expect(result.native.impressionTrackers[0]).to.equal(bidResponses.emptyAdomain.native.native.impressionTrackers[0]); + expect(result.native.clickTrackers[0]).to.equal(bidResponses.emptyAdomain.native.native.clickTrackers[0]); + expect(result.mediaType).to.equal(bidResponses.emptyAdomain.native.mediaType); + expect(result).to.not.have.any.keys('meta'); + expect(result).to.not.have.any.keys('advertiserDomains'); + }); + + it('handles banner responses for no adomain', function () { + const result = spec.interpretResponse({body: serverResponse.noAdomain.banner}, bidRequests.banner)[0]; + expect(result.requestId).to.equal(bidResponses.noAdomain.banner.requestId); + expect(result.width).to.equal(bidResponses.noAdomain.banner.width); + expect(result.height).to.equal(bidResponses.noAdomain.banner.height); + expect(result.creativeId).to.equal(bidResponses.noAdomain.banner.creativeId); + expect(result.dealId).to.equal(bidResponses.noAdomain.banner.dealId); + expect(result.currency).to.equal(bidResponses.noAdomain.banner.currency); + expect(result.netRevenue).to.equal(bidResponses.noAdomain.banner.netRevenue); + expect(result.ttl).to.equal(bidResponses.noAdomain.banner.ttl); + expect(result.ad).to.equal(bidResponses.noAdomain.banner.ad); + expect(result).to.not.have.any.keys('meta'); + expect(result).to.not.have.any.keys('advertiserDomains'); }); - it('handles native responses', function () { - const result = spec.interpretResponse({body: serverResponse.native}, bidRequests.native)[0]; - expect(result.requestId).to.equal(bidResponses.native.requestId); - expect(result.width).to.equal(bidResponses.native.width); - expect(result.height).to.equal(bidResponses.native.height); - expect(result.creativeId).to.equal(bidResponses.native.creativeId); - expect(result.dealId).to.equal(bidResponses.native.dealId); - expect(result.currency).to.equal(bidResponses.native.currency); - expect(result.netRevenue).to.equal(bidResponses.native.netRevenue); - expect(result.ttl).to.equal(bidResponses.native.ttl); - expect(result.native.title).to.equal(bidResponses.native.native.title); - expect(result.native.image.url).to.equal(bidResponses.native.native.image.url); - expect(result.native.image.height).to.equal(bidResponses.native.native.image.height); - expect(result.native.image.width).to.equal(bidResponses.native.native.image.width); - expect(result.native.icon.url).to.equal(bidResponses.native.native.icon.url); - expect(result.native.icon.width).to.equal(bidResponses.native.native.icon.width); - expect(result.native.icon.height).to.equal(bidResponses.native.native.icon.height); - expect(result.native.sponsoredBy).to.equal(bidResponses.native.native.sponsoredBy); - expect(result.native.body).to.equal(bidResponses.native.native.body); - expect(result.native.cta).to.equal(bidResponses.native.native.cta); - expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.native.native.privacyLink); - expect(result.native.clickUrl).to.equal(bidResponses.native.native.clickUrl); - expect(result.native.impressionTrackers[0]).to.equal(bidResponses.native.native.impressionTrackers[0]); - expect(result.native.clickTrackers[0]).to.equal(bidResponses.native.native.clickTrackers[0]); - expect(result.mediaType).to.equal(bidResponses.native.mediaType); + it('handles native responses for no adomain', function () { + const result = spec.interpretResponse({body: serverResponse.noAdomain.native}, bidRequests.native)[0]; + expect(result.requestId).to.equal(bidResponses.noAdomain.native.requestId); + expect(result.width).to.equal(bidResponses.noAdomain.native.width); + expect(result.height).to.equal(bidResponses.noAdomain.native.height); + expect(result.creativeId).to.equal(bidResponses.noAdomain.native.creativeId); + expect(result.dealId).to.equal(bidResponses.noAdomain.native.dealId); + expect(result.currency).to.equal(bidResponses.noAdomain.native.currency); + expect(result.netRevenue).to.equal(bidResponses.noAdomain.native.netRevenue); + expect(result.ttl).to.equal(bidResponses.noAdomain.native.ttl); + expect(result.native.title).to.equal(bidResponses.noAdomain.native.native.title); + expect(result.native.image.url).to.equal(bidResponses.noAdomain.native.native.image.url); + expect(result.native.image.height).to.equal(bidResponses.noAdomain.native.native.image.height); + expect(result.native.image.width).to.equal(bidResponses.noAdomain.native.native.image.width); + expect(result.native.icon.url).to.equal(bidResponses.noAdomain.native.native.icon.url); + expect(result.native.icon.width).to.equal(bidResponses.noAdomain.native.native.icon.width); + expect(result.native.icon.height).to.equal(bidResponses.noAdomain.native.native.icon.height); + expect(result.native.sponsoredBy).to.equal(bidResponses.noAdomain.native.native.sponsoredBy); + expect(result.native.body).to.equal(bidResponses.noAdomain.native.native.body); + expect(result.native.cta).to.equal(bidResponses.noAdomain.native.native.cta); + expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.noAdomain.native.native.privacyLink); + expect(result.native.clickUrl).to.equal(bidResponses.noAdomain.native.native.clickUrl); + expect(result.native.impressionTrackers[0]).to.equal(bidResponses.noAdomain.native.native.impressionTrackers[0]); + expect(result.native.clickTrackers[0]).to.equal(bidResponses.noAdomain.native.native.clickTrackers[0]); + expect(result.mediaType).to.equal(bidResponses.noAdomain.native.mediaType); + expect(result).to.not.have.any.keys('meta'); + expect(result).to.not.have.any.keys('advertiserDomains'); }); }); }); diff --git a/test/spec/modules/adglareBidAdapter_spec.js b/test/spec/modules/adglareBidAdapter_spec.js deleted file mode 100644 index d0dbe891f9d..00000000000 --- a/test/spec/modules/adglareBidAdapter_spec.js +++ /dev/null @@ -1,138 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/adglareBidAdapter.js'; - -describe('AdGlare Adapter Tests', function () { - let bidRequests; - - beforeEach(function () { - bidRequests = [ - { - bidder: 'adglare', - params: { - domain: 'try.engine.adglare.net', - zID: '475579334', - type: 'banner' - }, - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - }, - }, - bidId: '23acc48ad47af5', - auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ]; - }); - - describe('implementation', function () { - describe('for requests', function () { - it('should accept valid bid', function () { - let validBid = { - bidder: 'adglare', - params: { - domain: 'try.engine.adglare.net', - zID: '475579334', - type: 'banner' - } - }, - isValid = spec.isBidRequestValid(validBid); - - expect(isValid).to.equal(true); - }); - - it('should reject invalid bid', function () { - let invalidBid = { - bidder: 'adglare', - params: { - domain: 'somedomain.com', - zID: 'not an integer', - type: 'unsupported' - } - }, - isValid = spec.isBidRequestValid(invalidBid); - - expect(isValid).to.equal(false); - }); - - it('should build a valid endpoint URL', function () { - let bidRequests = [ - { - bidder: 'adglare', - params: { - domain: 'try.engine.adglare.net', - zID: '475579334', - type: 'banner' - }, - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - }, - }, - bidId: '23acc48ad47af5', - auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ], - bidderRequest = { - bidderCode: 'adglare', - auctionID: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - auctionStart: 1581497568252, - timeout: 5000, - refererInfo: { - referer: 'https://www.somedomain.com', - reachedTop: true, - numFrames: 0 - }, - start: 1581497568254 - }, - requests = spec.buildRequests(bidRequests, bidderRequest), - requestURL = requests[0].url; - - expect(requestURL).to.have.string('https://try.engine.adglare.net/?475579334'); - }); - }); - - describe('bid responses', function () { - it('should return complete bid response', function () { - let serverResponse = { - body: { - status: 'OK', - zID: 475579334, - cID: 501658124, - crID: 442123173, - cpm: 1.5, - ttl: 3600, - currency: 'USD', - width: 300, - height: 250, - adhtml: 'I am an ad.' - } - }, - bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); - - expect(bids).to.be.lengthOf(1); - expect(bids[0].bidderCode).to.equal('adglare'); - expect(bids[0].cpm).to.equal(1.5); - expect(bids[0].width).to.equal(300); - expect(bids[0].height).to.equal(250); - expect(bids[0].currency).to.equal('USD'); - expect(bids[0].netRevenue).to.equal(true); - }); - - it('should return empty bid response', function () { - let serverResponse = { - body: { - status: 'NOADS' - } - }, - bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); - - expect(bids).to.be.lengthOf(0); - }); - }); - }); -}); diff --git a/test/spec/modules/adhashBidAdapter_spec.js b/test/spec/modules/adhashBidAdapter_spec.js index ab4df84c093..6c214b84928 100644 --- a/test/spec/modules/adhashBidAdapter_spec.js +++ b/test/spec/modules/adhashBidAdapter_spec.js @@ -77,7 +77,7 @@ describe('adhashBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://bidder.adhash.org/rtb?version=1.0&prebid=true'); + expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=1.0&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); expect(result[0].bidRequest).to.equal(bidRequest); expect(result[0].data).to.have.property('timezone'); expect(result[0].data).to.have.property('location'); @@ -93,7 +93,7 @@ describe('adhashBidAdapter', function () { const result = spec.buildRequests([ bidRequest ], { gdprConsent: true }); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://bidder.adhash.org/rtb?version=1.0&prebid=true'); + expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=1.0&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); expect(result[0].bidRequest).to.equal(bidRequest); expect(result[0].data).to.have.property('timezone'); expect(result[0].data).to.have.property('location'); @@ -123,9 +123,8 @@ describe('adhashBidAdapter', function () { it('should interpret the response correctly', function () { const serverResponse = { body: { - creatives: [{ - costEUR: 1.234 - }] + creatives: [{ costEUR: 1.234 }], + advertiserDomains: 'adhash.org' } }; const result = spec.interpretResponse(serverResponse, request); @@ -138,6 +137,7 @@ describe('adhashBidAdapter', function () { expect(result[0].netRevenue).to.equal(true); expect(result[0].currency).to.equal('EUR'); expect(result[0].ttl).to.equal(60); + expect(result[0].meta.advertiserDomains).to.eql(['adhash.org']); }); it('should return empty array when there are no creatives returned', function () { diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index 526102c51fe..3fe0a62b2a0 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; import {spec} from 'modules/adheseBidAdapter.js'; +import {config} from 'src/config.js'; const BID_ID = 456; const TTL = 360; @@ -131,12 +132,21 @@ describe('AdheseAdapter', function () { expect(JSON.parse(req.data)).to.not.have.key('eids'); }); - it('should request vast content as url', function () { + it('should request vast content as url by default', function () { let req = spec.buildRequests([ minimalBid() ], bidderRequest); expect(JSON.parse(req.data).vastContentAsUrl).to.equal(true); }); + it('should request vast content as markup when configured', function () { + sinon.stub(config, 'getConfig').withArgs('adhese').returns({ vastContentAsUrl: false }); + + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).vastContentAsUrl).to.equal(false); + config.getConfig.restore(); + }); + it('should include bids', function () { let bid = minimalBid(); let req = spec.buildRequests([ bid ], bidderRequest); @@ -155,6 +165,22 @@ describe('AdheseAdapter', function () { expect(req.url).to.equal('https://ads-demo.adhese.com/json'); }); + + it('should include params specified in the config', function () { + sinon.stub(config, 'getConfig').withArgs('adhese').returns({ globalTargets: { 'tl': [ 'all' ] } }); + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).parameters).to.deep.include({ 'tl': [ 'all' ] }); + config.getConfig.restore(); + }); + + it('should give priority to bid params over config params', function () { + sinon.stub(config, 'getConfig').withArgs('adhese').returns({ globalTargets: { 'xt': ['CONFIG_CONSENT_STRING'] } }); + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).parameters).to.deep.include({ 'xt': [ 'CONSENT_STRING' ] }); + config.getConfig.restore(); + }); }); describe('interpretResponse', () => { @@ -186,7 +212,10 @@ describe('AdheseAdapter', function () { body: '
', tracker: 'https://hosts-demo.adhese.com/rtb_gateway/handlers/client/track/?id=a2f39296-6dd0-4b3c-be85-7baa22e7ff4a', impressionCounter: 'https://hosts-demo.adhese.com/rtb_gateway/handlers/client/track/?id=a2f39296-6dd0-4b3c-be85-7baa22e7ff4a', - extension: {'prebid': {'cpm': {'amount': '1.000000', 'currency': 'USD'}}, mediaType: 'banner'} + extension: {'prebid': {'cpm': {'amount': '1.000000', 'currency': 'USD'}}, mediaType: 'banner'}, + adomain: [ + 'www.example.com' + ] } ] }; @@ -217,7 +246,12 @@ describe('AdheseAdapter', function () { slotId: '10', slotName: '_main_page_-leaderboard' } - } + }, + meta: { + advertiserDomains: [ + 'www.example.com' + ] + }, }]; expect(spec.interpretResponse(sspBannerResponse, bidRequest)).to.deep.equal(expectedResponse); }); @@ -254,7 +288,10 @@ describe('AdheseAdapter', function () { origin: 'RUBICON', originInstance: '', originData: {} - } + }, + meta: { + advertiserDomains: [] + }, }]; expect(spec.interpretResponse(sspVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); @@ -291,7 +328,10 @@ describe('AdheseAdapter', function () { origin: 'RUBICON', originInstance: '', originData: {} - } + }, + meta: { + advertiserDomains: [] + }, }]; expect(spec.interpretResponse(sspCachedVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); @@ -369,6 +409,9 @@ describe('AdheseAdapter', function () { mediaType: 'banner', netRevenue: NET_REVENUE, ttl: TTL, + meta: { + advertiserDomains: [] + }, }]; expect(spec.interpretResponse(adheseBannerResponse, bidRequest)).to.deep.equal(expectedResponse); }); @@ -433,6 +476,9 @@ describe('AdheseAdapter', function () { mediaType: 'video', netRevenue: NET_REVENUE, ttl: TTL, + meta: { + advertiserDomains: [] + }, }]; expect(spec.interpretResponse(adheseVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); @@ -497,6 +543,9 @@ describe('AdheseAdapter', function () { mediaType: 'video', netRevenue: NET_REVENUE, ttl: TTL, + meta: { + advertiserDomains: [] + }, }]; expect(spec.interpretResponse(adheseVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); diff --git a/test/spec/modules/adkernelAdnBidAdapter_spec.js b/test/spec/modules/adkernelAdnBidAdapter_spec.js index d3c9a2cf816..c4ad134711a 100644 --- a/test/spec/modules/adkernelAdnBidAdapter_spec.js +++ b/test/spec/modules/adkernelAdnBidAdapter_spec.js @@ -107,7 +107,12 @@ describe('AdkernelAdn adapter', function () { bid: 5.0, tag: '', w: 300, - h: 250 + h: 250, + advertiserId: 777, + advertiserName: 'advertiser', + agencyName: 'agency', + advertiserDomains: ['example.com'], + primaryCatId: 'IAB1-1', }, { id: 'ad-unit-2', impid: '31d798477126c4', @@ -115,7 +120,12 @@ describe('AdkernelAdn adapter', function () { bid: 2.5, tag: '', w: 300, - h: 250 + h: 250, + advertiserId: 777, + advertiserName: 'advertiser', + agencyName: 'agency', + advertiserDomains: ['example.com'], + secondaryCatIds: ['IAB1-4', 'IAB8-16', 'IAB25-5'] }, { id: 'video_wrapper', impid: '57d602ad1c9545', @@ -375,6 +385,11 @@ describe('AdkernelAdn adapter', function () { expect(resp).to.have.property('mediaType', 'banner'); expect(resp).to.have.property('ad'); expect(resp.ad).to.have.string(''); + expect(resp.meta.advertiserId).to.be.eql(777); + expect(resp.meta.advertiserName).to.be.eql('advertiser'); + expect(resp.meta.agencyName).to.be.eql('agency'); + expect(resp.meta.advertiserDomains).to.be.eql(['example.com']); + expect(resp.meta.primaryCatId).to.be.eql('IAB1-1'); }); it('should return fully-initialized video bid-response', function () { diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index b9d647238bf..ab1c5501bd9 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -28,7 +28,15 @@ describe('Adkernel adapter', function () { banner: { sizes: [[728, 90]] } - } + }, + userIdAsEids: [ + { + source: 'crwdcntrl.net', + uids: [ + {atype: 1, id: '97d09fbba28542b7acbb6317c9534945a702b74c5993c352f332cfe83f40cdd9'} + ] + } + ] }, bid3_host2 = { bidder: 'adkernel', params: {zoneId: 1, host: 'rtb-private.adkernel.com'}, @@ -251,6 +259,7 @@ describe('Adkernel adapter', function () { function buildRequest(bidRequests, bidderRequest = DEFAULT_BIDDER_REQUEST, dnt = true) { let dntmock = sandbox.stub(utils, 'getDNT').callsFake(() => dnt); + bidderRequest.bids = bidRequests; let pbRequests = spec.buildRequests(bidRequests, bidderRequest); dntmock.restore(); let rtbRequests = pbRequests.map(r => JSON.parse(r.data)); @@ -381,6 +390,17 @@ describe('Adkernel adapter', function () { let [_, bidRequests] = buildRequest([bid]); expect(bidRequests[0].imp[0]).to.have.property('bidfloor', 0.145); }); + + it('should forward user ids if available', function() { + let bid = Object.assign({}, bid2_zone2); + let [_, bidRequests] = buildRequest([bid]); + expect(bidRequests[0]).to.have.property('user'); + expect(bidRequests[0].user).to.have.property('ext'); + expect(bidRequests[0].user.ext).to.have.property('eids'); + expect(bidRequests[0].user.ext.eids).to.be.an('array').that.is.not.empty; + expect(bidRequests[0].user.ext.eids[0]).to.have.property('source'); + expect(bidRequests[0].user.ext.eids[0]).to.have.property('uids'); + }); }); describe('video request building', function () { diff --git a/test/spec/modules/adliveBidAdapter_spec.js b/test/spec/modules/adliveBidAdapter_spec.js deleted file mode 100644 index ddf8f82f20f..00000000000 --- a/test/spec/modules/adliveBidAdapter_spec.js +++ /dev/null @@ -1,78 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/adliveBidAdapter.js'; - -describe('adliveBidAdapterTests', function() { - let bidRequestData = { - bids: [ - { - bidId: 'transaction_1234', - bidder: 'adlive', - params: { - hashes: ['1e100887dd614b0909bf6c49ba7f69fdd1360437'] - }, - sizes: [[300, 250]] - } - ] - }; - let request = []; - - it('validate_pub_params', function() { - expect( - spec.isBidRequestValid({ - bidder: 'adlive', - params: { - hashes: ['1e100887dd614b0909bf6c49ba7f69fdd1360437'] - } - }) - ).to.equal(true); - }); - - it('validate_generated_params', function() { - request = spec.buildRequests(bidRequestData.bids); - let req_data = JSON.parse(request[0].data); - - expect(req_data.transaction_id).to.equal('transaction_1234'); - }); - - it('validate_response_params', function() { - let serverResponse = { - body: [ - { - hash: '1e100887dd614b0909bf6c49ba7f69fdd1360437', - content: 'Ad html', - price: 1.12, - size: [300, 250], - is_passback: 0 - } - ] - }; - - let bids = spec.interpretResponse(serverResponse, bidRequestData.bids[0]); - expect(bids).to.have.lengthOf(1); - - let bid = bids[0]; - - expect(bid.creativeId).to.equal('1e100887dd614b0909bf6c49ba7f69fdd1360437'); - expect(bid.ad).to.equal('Ad html'); - expect(bid.cpm).to.equal(1.12); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.currency).to.equal('USD'); - }); - - it('validate_response_params_with passback', function() { - let serverResponse = { - body: [ - { - hash: '1e100887dd614b0909bf6c49ba7f69fdd1360437', - content: 'Ad html passback', - size: [300, 250], - is_passback: 1 - } - ] - }; - let bids = spec.interpretResponse(serverResponse, bidRequestData.bids[0]); - - expect(bids).to.have.lengthOf(0); - }); -}); diff --git a/test/spec/modules/adlooxAdServerVideo_spec.js b/test/spec/modules/adlooxAdServerVideo_spec.js new file mode 100644 index 00000000000..bf8fe9d43f0 --- /dev/null +++ b/test/spec/modules/adlooxAdServerVideo_spec.js @@ -0,0 +1,339 @@ +import adapterManager from 'src/adapterManager.js'; +import analyticsAdapter from 'modules/adlooxAnalyticsAdapter.js'; +import { ajax } from 'src/ajax.js'; +import { buildVideoUrl } from 'modules/adlooxAdServerVideo.js'; +import { expect } from 'chai'; +import events from 'src/events.js'; +import { targeting } from 'src/targeting.js'; +import * as utils from 'src/utils.js'; + +const analyticsAdapterName = 'adloox'; + +describe('Adloox Ad Server Video', function () { + let sandbox; + + const adUnit = { + code: 'ad-slot-1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [ 640, 480 ] + } + }, + bids: [ + { + bidder: 'dummy' + } + ] + }; + + const bid = { + bidder: adUnit.bids[0].bidder, + adUnitCode: adUnit.code, + mediaType: 'video' + }; + + const analyticsOptions = { + js: 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js', + client: 'adlooxtest', + clientid: 127, + platformid: 0, + tagid: 0 + }; + + const urlVAST = 'https://j.adlooxtracking.com/ads/vast/tag.php'; + + const creativeUrl = 'http://example.invalid/c'; + const vastHeaders = { + 'content-type': 'application/xml;charset=utf-8' + }; + + const vastUrl = 'http://example.invalid/w'; + const vastContent = +` + + + + + + + 00:00:30 + + http://example.invalid/v.js + + + + + + + + + + + + 00:00:30 + + ${creativeUrl} + + + + + + +` + + const wrapperUrl = 'http://example.invalid/w'; + const wrapperContent = +` + + + + + + +`; + + adapterManager.registerAnalyticsAdapter({ + code: analyticsAdapterName, + adapter: analyticsAdapter + }); + + before(function () { + sandbox = sinon.sandbox.create(); + sandbox.stub(events, 'getEvents').returns([]); + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptions + }); + expect(analyticsAdapter.context).is.not.null; + }); + + after(function () { + analyticsAdapter.disableAnalytics(); + expect(analyticsAdapter.context).is.null; + + sandbox.restore(); + sandbox = undefined; + }); + + describe('buildVideoUrl', function () { + describe('invalid arguments', function () { + it('should require callback', function (done) { + const ret = buildVideoUrl(); + + expect(ret).is.false; + + done(); + }); + + it('should require options', function (done) { + const ret = buildVideoUrl(undefined, function () {}); + + expect(ret).is.false; + + done(); + }); + + it('should reject non-string options.url_vast', function (done) { + const ret = buildVideoUrl({ url_vast: null }, function () {}); + + expect(ret).is.false; + + done(); + }); + + it('should require options.adUnit or options.bid', function (done) { + let BID = utils.deepClone(bid); + + let getWinningBidsStub = sinon.stub(targeting, 'getWinningBids') + getWinningBidsStub.withArgs(adUnit.code).returns([ BID ]); + + const ret = buildVideoUrl({ url: vastUrl }, function () {}); + expect(ret).is.false; + + const retAdUnit = buildVideoUrl({ url: vastUrl, adUnit: utils.deepClone(adUnit) }, function () {}) + expect(retAdUnit).is.true; + + const retBid = buildVideoUrl({ url: vastUrl, bid: BID }, function () {}); + expect(retBid).is.true; + + getWinningBidsStub.restore(); + + done(); + }); + + it('should reject non-string options.url', function (done) { + const ret = buildVideoUrl({ url: null, bid: utils.deepClone(bid) }, function () {}); + + expect(ret).is.false; + + done(); + }); + + it('should reject non-boolean options.wrap', function (done) { + const ret = buildVideoUrl({ wrap: null, url: vastUrl, bid: utils.deepClone(bid) }, function () {}); + + expect(ret).is.false; + + done(); + }); + + it('should reject non-boolean options.blob', function (done) { + const ret = buildVideoUrl({ blob: null, url: vastUrl, bid: utils.deepClone(bid) }, function () {}); + + expect(ret).is.false; + + done(); + }); + }); + + describe('process VAST', function () { + let server = null; + let BID = null; + let getWinningBidsStub; + beforeEach(function () { + server = sinon.createFakeServer(); + BID = utils.deepClone(bid); + getWinningBidsStub = sinon.stub(targeting, 'getWinningBids') + getWinningBidsStub.withArgs(adUnit.code).returns([ BID ]); + }); + afterEach(function () { + getWinningBidsStub.restore(); + getWinningBidsStub = undefined; + BID = null; + server.restore(); + server = null; + }); + + it('should return URL unchanged for non-VAST', function (done) { + const ret = buildVideoUrl({ url: vastUrl, bid: BID }, function (url) { + expect(url).is.equal(vastUrl); + + done(); + }); + expect(ret).is.true; + + const request = server.requests[0]; + expect(request.url).is.equal(vastUrl); + request.respond(200, { 'content-type': 'application/octet-stream' }, 'notvast'); + }); + + it('should return URL unchanged for empty VAST', function (done) { + const ret = buildVideoUrl({ url: vastUrl, bid: BID }, function (url) { + expect(url).is.equal(vastUrl); + + done(); + }); + expect(ret).is.true; + + const request = server.requests[0]; + expect(request.url).is.equal(vastUrl); + request.respond(200, vastHeaders, '\n'); + }); + + it('should fetch, retry on withoutCredentials, follow and return a wrapped blob that expires', function (done) { + BID.responseTimestamp = utils.timestamp(); + BID.ttl = 30; + + const clock = sandbox.useFakeTimers(BID.responseTimestamp); + + const options = { + url_vast: urlVAST, + url: wrapperUrl, + bid: BID + }; + const ret = buildVideoUrl(options, function (url) { + expect(url.substr(0, options.url_vast.length)).is.equal(options.url_vast); + + const match = url.match(/[?&]vast=(blob%3A[^&]+)/); + expect(match).is.not.null; + + const blob = decodeURIComponent(match[1]); + + const xfr = sandbox.useFakeXMLHttpRequest(); + xfr.useFilters = true; + xfr.addFilter(x => true); // there is no network traffic for Blob URLs here + + ajax(blob, { + success: (responseText, q) => { + expect(q.status).is.equal(200); + expect(q.getResponseHeader('content-type')).is.equal(vastHeaders['content-type']); + + clock.runAll(); + + ajax(blob, { + success: (responseText, q) => { + xfr.useFilters = false; // .restore() does not really work + if (q.status == 0) return done(); + done(new Error('Blob should have expired')); + }, + error: (statusText, q) => { + xfr.useFilters = false; + done(); + } + }); + }, + error: (statusText, q) => { + xfr.useFilters = false; + done(new Error(statusText)); + } + }); + }); + expect(ret).is.true; + + const wrapperRequestCORS = server.requests[0]; + expect(wrapperRequestCORS.url).is.equal(wrapperUrl); + expect(wrapperRequestCORS.withCredentials).is.true; + wrapperRequestCORS.respond(0); + + const wrapperRequest = server.requests[1]; + expect(wrapperRequest.url).is.equal(wrapperUrl); + expect(wrapperRequest.withCredentials).is.false; + wrapperRequest.respond(200, vastHeaders, wrapperContent); + + const vastRequest = server.requests[2]; + expect(vastRequest.url).is.equal(vastUrl); + vastRequest.respond(200, vastHeaders, vastContent); + }); + + it('should fetch, follow and return a wrapped non-blob', function (done) { + const options = { + url_vast: urlVAST, + url: wrapperUrl, + bid: BID, + blob: false + }; + const ret = buildVideoUrl(options, function (url) { + expect(url.substr(0, options.url_vast.length)).is.equal(options.url_vast); + expect(/[?&]vast=blob%3/.test(url)).is.false; + + done(); + }); + expect(ret).is.true; + + const wrapperRequest = server.requests[0]; + expect(wrapperRequest.url).is.equal(wrapperUrl); + wrapperRequest.respond(200, vastHeaders, wrapperContent); + + const vastRequest = server.requests[1]; + expect(vastRequest.url).is.equal(vastUrl); + vastRequest.respond(200, vastHeaders, vastContent); + }); + + it('should not follow and return URL unchanged for non-wrap', function (done) { + const options = { + url: wrapperUrl, + bid: BID, + wrap: false + }; + const ret = buildVideoUrl(options, function (url) { + expect(url).is.equal(options.url); + + done(); + }); + expect(ret).is.true; + }); + }); + }); +}); diff --git a/test/spec/modules/adlooxAnalyticsAdapter_spec.js b/test/spec/modules/adlooxAnalyticsAdapter_spec.js index 9cc9c4ded4d..b87fd20adc2 100644 --- a/test/spec/modules/adlooxAnalyticsAdapter_spec.js +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -3,7 +3,7 @@ import analyticsAdapter, { command as analyticsCommand, COMMAND } from 'modules/ import { AUCTION_COMPLETED } from 'src/auction.js'; import { expect } from 'chai'; import events from 'src/events.js'; -import { EVENTS } from 'src/constants.json'; +import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; @@ -35,7 +35,7 @@ describe('Adloox Analytics Adapter', function () { tagid: 0, params: { dummy1: '%%client%%', - dummy2: '%%pbAdSlot%%', + dummy2: '%%pbadslot%%', dummy3: function(bid) { throw new Error(esplode) } } }; @@ -143,10 +143,10 @@ describe('Adloox Analytics Adapter', function () { return arg.tagName === 'LINK' && arg.getAttribute('rel') === 'preload' && arg.getAttribute('as') === 'script' && href_uri.href === uri.href; }; - events.emit(EVENTS.AUCTION_END, auctionDetails); + events.emit(CONSTANTS.EVENTS.AUCTION_END, auctionDetails); expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.true; - events.emit(EVENTS.AUCTION_END, auctionDetails); + events.emit(CONSTANTS.EVENTS.AUCTION_END, auctionDetails); expect(insertElementStub.callCount).to.equal(1); done(); @@ -167,7 +167,7 @@ describe('Adloox Analytics Adapter', function () { const querySelectorStub = sandbox.stub(document, 'querySelector'); querySelectorStub.withArgs(`#${bid.adUnitCode}`).returns(slot); - events.emit(EVENTS.BID_WON, bid); + events.emit(CONSTANTS.EVENTS.BID_WON, bid); const [urlInserted, moduleCode] = loadExternalScriptStub.getCall(0).args; @@ -178,6 +178,30 @@ describe('Adloox Analytics Adapter', function () { done(); }); + + it('should not inject verification JS on BID_WON when handled via Ad Server module', function (done) { + const bidIgnore = utils.deepClone(bid); + utils.deepSetValue(bidIgnore, 'ext.adloox.video.adserver', true); + + const parent = document.createElement('div'); + + const slot = document.createElement('div'); + slot.id = bidIgnore.adUnitCode; + parent.appendChild(slot); + + const script = document.createElement('script'); + const createElementStub = sandbox.stub(document, 'createElement'); + createElementStub.withArgs('script').returns(script); + + const querySelectorStub = sandbox.stub(document, 'querySelector'); + querySelectorStub.withArgs(`#${bid.adUnitCode}`).returns(slot); + + events.emit(CONSTANTS.EVENTS.BID_WON, bidIgnore); + + expect(parent.querySelector('script')).is.null; + + done(); + }); }); describe('command', function () { @@ -214,7 +238,7 @@ describe('Adloox Analytics Adapter', function () { it('should inject tracking event', function (done) { const data = { - eventType: EVENTS.BID_WON, + eventType: CONSTANTS.EVENTS.BID_WON, args: bid }; diff --git a/test/spec/modules/adlooxRtdProvider_spec.js b/test/spec/modules/adlooxRtdProvider_spec.js new file mode 100644 index 00000000000..c0438c45451 --- /dev/null +++ b/test/spec/modules/adlooxRtdProvider_spec.js @@ -0,0 +1,313 @@ +import adapterManager from 'src/adapterManager.js'; +import analyticsAdapter from 'modules/adlooxAnalyticsAdapter.js'; +import { config as _config } from 'src/config.js'; +import { expect } from 'chai'; +import events from 'src/events.js'; +import * as prebidGlobal from 'src/prebidGlobal.js'; +import { subModuleObj as rtdProvider } from 'modules/adlooxRtdProvider.js'; +import * as utils from 'src/utils.js'; + +const analyticsAdapterName = 'adloox'; + +describe('Adloox RTD Provider', function () { + let sandbox; + + const adUnit = { + code: 'ad-slot-1', + ortb2Imp: { + ext: { + data: { + pbadslot: '/123456/home/ad-slot-1' + } + } + }, + mediaTypes: { + banner: { + sizes: [ [300, 250] ] + } + }, + bids: [ + { + bidder: 'dummy' + } + ] + }; + + const analyticsOptions = { + js: 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js', + client: 'adlooxtest', + clientid: 127, + platformid: 0, + tagid: 0 + }; + + const config = {}; + + adapterManager.registerAnalyticsAdapter({ + code: analyticsAdapterName, + adapter: analyticsAdapter + }); + + before(function () { + sandbox = sinon.sandbox.create(); + sandbox.stub(events, 'getEvents').returns([]); + }); + + after(function () { + sandbox.restore(); + sandbox = undefined; + }); + + describe('invalid config', function () { + it('should require config', function (done) { + const ret = rtdProvider.init(); + + expect(ret).is.false; + + done(); + }); + + it('should reject non-object config.params', function (done) { + const ret = rtdProvider.init({ params: null }); + + expect(ret).is.false; + + done(); + }); + + it('should reject non-string config.params.api_origin', function (done) { + const ret = rtdProvider.init({ params: { api_origin: null } }); + + expect(ret).is.false; + + done(); + }); + + it('should reject less than one config.params.imps', function (done) { + const ret = rtdProvider.init({ params: { imps: 0 } }); + + expect(ret).is.false; + + done(); + }); + + it('should reject negative config.params.freqcap_ip', function (done) { + const ret = rtdProvider.init({ params: { freqcap_ip: -1 } }); + + expect(ret).is.false; + + done(); + }); + + it('should reject negative integer config.params.freqcap_ipua', function (done) { + const ret = rtdProvider.init({ params: { freqcap_ipua: -1 } }); + + expect(ret).is.false; + + done(); + }); + + it('should reject non-array of integers with value greater than zero config.params.thresholds', function (done) { + const ret = rtdProvider.init({ params: { thresholds: [ 70, null ] } }); + + expect(ret).is.false; + + done(); + }); + + it('should reject non-boolean config.params.slotinpath', function (done) { + const ret = rtdProvider.init({ params: { slotinpath: null } }); + + expect(ret).is.false; + + done(); + }); + + it('should reject invalid config.params.params (legacy/deprecated)', function (done) { + const ret = rtdProvider.init({ params: { params: { clientid: 0, tagid: 0 } } }); + + expect(ret).is.false; + + done(); + }); + }); + + describe('process segments', function () { + before(function () { + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptions + }); + expect(analyticsAdapter.context).is.not.null; + }); + + after(function () { + analyticsAdapter.disableAnalytics(); + expect(analyticsAdapter.context).is.null; + }); + + let server = null; + let __config = null, CONFIG = null; + let getConfigStub, setConfigStub; + beforeEach(function () { + server = sinon.createFakeServer(); + __config = {}; + CONFIG = utils.deepClone(config); + getConfigStub = sinon.stub(_config, 'getConfig').callsFake(function (path) { + return utils.deepAccess(__config, path); + }); + setConfigStub = sinon.stub(_config, 'setConfig').callsFake(function (obj) { + utils.mergeDeep(__config, obj); + }); + }); + afterEach(function () { + setConfigStub.restore(); + getConfigStub.restore(); + getConfigStub = setConfigStub = undefined; + CONFIG = null; + __config = null; + server.restore(); + server = null; + }); + + it('should fetch segments', function (done) { + const adUnitWithSegments = utils.deepClone(adUnit); + const getGlobalStub = sinon.stub(prebidGlobal, 'getGlobal').returns({ + adUnits: [ adUnitWithSegments ] + }); + + const ret = rtdProvider.init(CONFIG); + expect(ret).is.true; + + const callback = function () { + expect(__config.ortb2.site.ext.data.adloox_rtd.ok).is.true; + expect(__config.ortb2.site.ext.data.adloox_rtd.nope).is.undefined; + expect(__config.ortb2.user.ext.data.adloox_rtd.unused).is.false; + expect(__config.ortb2.user.ext.data.adloox_rtd.nope).is.undefined; + expect(adUnitWithSegments.ortb2Imp.ext.data.adloox_rtd.dis.length).is.equal(3); + expect(adUnitWithSegments.ortb2Imp.ext.data.adloox_rtd.nope).is.undefined; + + getGlobalStub.restore(); + + done(); + }; + rtdProvider.getBidRequestData({}, callback, CONFIG, null); + + const request = server.requests[0]; + const response = { unused: false, _: [ { d: 77 } ] }; + request.respond(200, { 'content-type': 'application/json' }, JSON.stringify(response)); + }); + + it('should set ad server targeting', function (done) { + utils.deepSetValue(__config, 'ortb2.site.ext.data.adloox_rtd.ok', true); + + const adUnitWithSegments = utils.deepClone(adUnit); + utils.deepSetValue(adUnitWithSegments, 'ortb2Imp.ext.data.adloox_rtd.dis', [ 50, 60 ]); + const getGlobalStub = sinon.stub(prebidGlobal, 'getGlobal').returns({ + adUnits: [ adUnitWithSegments ] + }); + + const targetingData = rtdProvider.getTargetingData([ adUnitWithSegments.code ], CONFIG); + expect(Object.keys(targetingData).length).is.equal(1); + expect(Object.keys(targetingData[adUnit.code]).length).is.equal(2); + expect(targetingData[adUnit.code].adl_ok).is.equal(1); + expect(targetingData[adUnit.code].adl_dis.length).is.equal(2); + + getGlobalStub.restore(); + + done(); + }); + }); + + describe('measure atf', function () { + const adUnitCopy = utils.deepClone(adUnit); + + const ratio = 0.38; + const [ [width, height] ] = utils.getAdUnitSizes(adUnitCopy); + + before(function () { + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptions + }); + expect(analyticsAdapter.context).is.not.null; + }); + + after(function () { + analyticsAdapter.disableAnalytics(); + expect(analyticsAdapter.context).is.null; + }); + + it(`should return ${ratio} for same-origin`, function (done) { + const el = document.createElement('div'); + el.setAttribute('id', adUnitCopy.code); + + const offset = height * ratio; + const elStub = sinon.stub(el, 'getBoundingClientRect').returns({ + top: 0 - (height - offset), + bottom: height - offset, + left: 0, + right: width + }); + + const querySelectorStub = sinon.stub(document, 'querySelector'); + querySelectorStub.withArgs(`#${adUnitCopy.code}`).returns(el); + + rtdProvider.atf(adUnitCopy, function(x) { + expect(x).is.equal(ratio); + + querySelectorStub.restore(); + elStub.restore(); + + done(); + }); + }); + + ('IntersectionObserver' in window ? it : it.skip)(`should return ${ratio} for cross-origin`, function (done) { + const frameElementStub = sinon.stub(window, 'frameElement').value(null); + + const el = document.createElement('div'); + el.setAttribute('id', adUnitCopy.code); + + const elStub = sinon.stub(el, 'getBoundingClientRect').returns({ + top: 0, + bottom: height, + left: 0, + right: width + }); + + const querySelectorStub = sinon.stub(document, 'querySelector'); + querySelectorStub.withArgs(`#${adUnitCopy.code}`).returns(el); + + let intersectionObserverStubFn = null; + const intersectionObserverStub = sinon.stub(window, 'IntersectionObserver').callsFake((fn) => { + intersectionObserverStubFn = fn; + return { + observe: (element) => { + expect(element).is.equal(el); + + intersectionObserverStubFn([{ + target: element, + intersectionRect: { width, height }, + intersectionRatio: ratio + }]); + }, + unobserve: (element) => { + expect(element).is.equal(el); + } + } + }); + + rtdProvider.atf(adUnitCopy, function(x) { + expect(x).is.equal(ratio); + + intersectionObserverStub.restore(); + querySelectorStub.restore(); + elStub.restore(); + frameElementStub.restore(); + + done(); + }); + }); + }); +}); diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index f3212dec2f5..33e6a8a89b9 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -1,28 +1,64 @@ import {expect} from 'chai'; import {spec} from '../../../modules/admanBidAdapter.js'; -describe('AdmanMediaBidAdapter', function () { +describe('AdmanAdapter', function () { let bid = { - bidId: '23fhj33i987f', + bidId: '2dd581a2b6281d', bidder: 'adman', + bidderRequestId: '145e1d6a7837c9', params: { - placementId: 0, - traffic: 'banner' + placementId: 0 + }, + placementCode: 'placementid_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '0', + hp: 1, + rid: 'bidrequestid', + // name: 'alladsallthetime', + domain: 'example.com' + } + ] } }; + let bidderRequest = { + bidderCode: 'adman', + auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', + bidderRequestId: 'ffffffffffffff', + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000, + uspConsent: '1YN-', + refererInfo: { + referer: 'http://www.example.com', + reachedTop: true, + }, + bids: [bid] + } describe('isBidRequestValid', function () { - it('Should return true if there are bidId, params and placementId parameters present', function () { + it('Should return true when placementId can be cast to a number', function () { expect(spec.isBidRequestValid(bid)).to.be.true; }); - it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; + it('Should return false when placementId is not a number', function () { + bid.params.placementId = 'aaa'; expect(spec.isBidRequestValid(bid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid]); + let serverRequest = spec.buildRequests([bid], bidderRequest); it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; @@ -35,21 +71,31 @@ describe('AdmanMediaBidAdapter', function () { it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://pub.admanmedia.com/?c=o&m=multi'); }); + it('Should contain ccpa', function() { + expect(serverRequest.data.ccpa).to.be.an('string') + }) + it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('banner'); + let placements = data['placements']; + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'bidFloor'); + expect(placement.schain).to.be.an('object') + expect(placement.placementId).to.be.a('number'); + expect(placement.bidId).to.be.a('string'); + expect(placement.traffic).to.be.a('string'); + expect(placement.sizes).to.be.an('array'); + expect(placement.bidFloor).to.be.an('number'); + } }); it('Returns empty data if no valid requests are passed', function () { serverRequest = spec.buildRequests([]); @@ -57,175 +103,85 @@ describe('AdmanMediaBidAdapter', function () { expect(data.placements).to.be.an('array').that.is.empty; }); }); - describe('interpretResponse', function () { - it('Should interpret banner response', function () { - const banner = { - body: [{ - mediaType: 'banner', - width: 300, - height: 250, - cpm: 0.4, - ad: 'Test', - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] - }; - let bannerResponses = spec.interpretResponse(banner); - expect(bannerResponses).to.be.an('array').that.is.not.empty; - let dataItem = bannerResponses[0]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); - }); - it('Should interpret video response', function () { - const video = { - body: [{ - vastUrl: 'test.com', - mediaType: 'video', - cpm: 0.5, - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] - }; - let videoResponses = spec.interpretResponse(video); - expect(videoResponses).to.be.an('array').that.is.not.empty; - let dataItem = videoResponses[0]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.5); - expect(dataItem.vastUrl).to.equal('test.com'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); - }); - it('Should interpret native response', function () { - const native = { - body: [{ - mediaType: 'native', - native: { - clickUrl: 'test.com', - title: 'Test', - image: 'test.com', - impressionTrackers: ['test.com'], - }, - ttl: 120, - cpm: 0.4, - requestId: '23fhj33i987f', - creativeId: '2', - netRevenue: true, - currency: 'USD', - }] - }; - let nativeResponses = spec.interpretResponse(native); - expect(nativeResponses).to.be.an('array').that.is.not.empty; - - let dataItem = nativeResponses[0]; - expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); - expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.native.clickUrl).to.equal('test.com'); - expect(dataItem.native.title).to.equal('Test'); - expect(dataItem.native.image).to.equal('test.com'); - expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; - expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + describe('buildRequests with user ids', function () { + bid.userId = {} + bid.userId.uid2 = { id: 'uid2id123' }; + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + let placements = data['placements']; + expect(data).to.be.an('object'); + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.property('eids') + expect(placement.eids).to.be.an('array') + expect(placement.eids.length).to.be.equal(1) + for (let index in placement.eids) { + let v = placement.eids[index]; + expect(v).to.have.all.keys('source', 'uids') + expect(v.source).to.be.oneOf(['uidapi.com']) + expect(v.uids).to.be.an('array'); + expect(v.uids.length).to.be.equal(1) + expect(v.uids[0]).to.have.property('id') + } + } }); - it('Should return an empty array if invalid banner response is passed', function () { - const invBanner = { - body: [{ - width: 300, - cpm: 0.4, - ad: 'Test', - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] - }; + }); - let serverResponses = spec.interpretResponse(invBanner); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - it('Should return an empty array if invalid video response is passed', function () { - const invVideo = { - body: [{ - mediaType: 'video', - cpm: 0.5, - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] - }; - let serverResponses = spec.interpretResponse(invVideo); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - it('Should return an empty array if invalid native response is passed', function () { - const invNative = { - body: [{ - mediaType: 'native', - clickUrl: 'test.com', - title: 'Test', - impressionTrackers: ['test.com'], - ttl: 120, - requestId: '23fhj33i987f', - creativeId: '2', - netRevenue: true, - currency: 'USD', - }] - }; - let serverResponses = spec.interpretResponse(invNative); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - it('Should return an empty array if invalid response is passed', function () { - const invalid = { - body: [{ - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] - }; - let serverResponses = spec.interpretResponse(invalid); - expect(serverResponses).to.be.an('array').that.is.empty; + describe('interpretResponse', function () { + let resObject = { + body: [ { + requestId: '123', + mediaType: 'banner', + cpm: 0.3, + width: 320, + height: 50, + ad: '

Hello ad

', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + } ] + }; + let serverResponses = spec.interpretResponse(resObject); + it('Returns an array of valid server responses if response object is valid', function () { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType', 'meta'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + } + it('Returns an empty array if invalid response is passed', function () { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); }); }); + describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs(); + let userSync = spec.getUserSyncs({}); it('Returns valid URL and type', function () { expect(userSync).to.be.an('array').with.lengthOf(1); expect(userSync[0].type).to.exist; expect(userSync[0].url).to.exist; expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://pub.admanmedia.com/?c=o&m=sync'); + expect(userSync[0].url).to.be.equal('https://pub.admanmedia.com/image?pbjs=1&coppa=0'); }); }); }); diff --git a/test/spec/modules/admediaBidAdapter_spec.js b/test/spec/modules/admediaBidAdapter_spec.js deleted file mode 100644 index 5dc7b9a02a8..00000000000 --- a/test/spec/modules/admediaBidAdapter_spec.js +++ /dev/null @@ -1,138 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/admediaBidAdapter.js'; - -describe('admediaAdapterTests', function () { - describe('bidRequestValidity', function () { - it('bidRequest with aid', function () { - expect(spec.isBidRequestValid({ - bidder: 'admedia', - params: { - aid: 86858, - } - })).to.equal(true); - }); - - it('bidRequest without aid', function () { - expect(spec.isBidRequestValid({ - bidder: 'a4g', - params: { - key: 86858 - } - })).to.equal(false); - }); - }); - - describe('bidRequest', function () { - const validBidRequests = [{ - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'auctionId': 'e3010a3c-5b95-4475-9ba2-1b004c737c30', - 'bidId': '2758de47c84ab58', - 'bidRequestsCount': 1, - 'bidder': 'admedia', - 'bidderRequestId': '1033407c6af0c7', - 'params': { - 'aid': 86858, - }, - 'sizes': [[300, 250], [300, 600]], - 'transactionId': '5851b2cf-ee2d-4022-abd2-d581ef01604e' - }, { - 'adUnitCode': 'div-gpt-ad-1460505748561-1', - 'auctionId': 'e3010a3c-5b95-4475-9ba2-1b004c737c30', - 'bidId': '3d2aaa400371fa', - 'bidRequestsCount': 1, - 'bidder': 'admedia', - 'bidderRequestId': '1033407c6af0c7', - 'params': { - 'aid': 84977, - }, - 'sizes': [[728, 90]], - 'transactionId': 'f8b5247e-7715-4e60-9d51-33153e78c190' - }]; - - const bidderRequest = { - 'auctionId': 'e3010a3c-5b95-4475-9ba2-1b004c737c30', - 'bidderCode': 'admedia', - 'bidderRequestId': '1033407c6af0c7', - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'https://test.com/index.html?pbjs_debug=true' - } - - }; - - const request = spec.buildRequests(validBidRequests, bidderRequest); - - it('bidRequest method', function () { - expect(request.method).to.equal('POST'); - }); - - it('bidRequest url', function () { - expect(request.url).to.equal('https://prebid.admedia.com/bidder/'); - }); - - it('bidRequest data', function () { - const data = JSON.parse(request.data); - expect(decodeURIComponent(data.referer)).to.be.eql(bidderRequest.refererInfo.referer); - expect(data.tags).to.be.an('array'); - expect(data.tags[0].aid).to.be.eql(validBidRequests[0].params.aid); - expect(data.tags[0].id).to.be.eql(validBidRequests[0].bidId); - expect(data.tags[0].sizes).to.be.eql(validBidRequests[0].sizes); - expect(data.tags[1].aid).to.be.eql(validBidRequests[1].params.aid); - expect(data.tags[1].id).to.be.eql(validBidRequests[1].bidId); - expect(data.tags[1].sizes).to.be.eql(validBidRequests[1].sizes); - }); - }); - - describe('interpretResponse', function () { - const serverResponse = { - body: { - tags: [ - { - ad: '', - cpm: 0.9, - height: 250, - id: '5582180864bc41', - width: 300, - }, - { - error: 'Error message', - id: '6dc6ee4e157749' - }, - { - ad: '', - cpm: 0, - height: 728, - id: '5762180864bc41', - width: 90, - } - ] - }, - headers: {} - }; - - const bidRequest = {}; - - const result = spec.interpretResponse(serverResponse, bidRequest); - - it('Should return an empty array if empty or no tags in response', function () { - expect(spec.interpretResponse({body: ''}, {}).length).to.equal(0); - }); - - it('Should have only one bid', function () { - expect(result.length).to.equal(1); - }); - - it('Should have required keys', function () { - expect(result[0].requestId).to.be.eql(serverResponse.body.tags[0].id); - expect(result[0].cpm).to.be.eql(serverResponse.body.tags[0].cpm); - expect(result[0].width).to.be.eql(serverResponse.body.tags[0].width); - expect(result[0].height).to.be.eql(serverResponse.body.tags[0].height); - expect(result[0].creativeId).to.be.eql(serverResponse.body.tags[0].id); - expect(result[0].dealId).to.be.eql(serverResponse.body.tags[0].id); - expect(result[0].netRevenue).to.be.eql(true); - expect(result[0].ttl).to.be.eql(120); - expect(result[0].ad).to.be.eql(serverResponse.body.tags[0].ad); - }) - }); -}); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 030e98d23f9..6dfde0d0652 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -4,7 +4,7 @@ import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from '../../../src/config.js'; const BIDDER_CODE = 'admixer'; -const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.1.aspx'; +const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; const ENDPOINT_URL_CUSTOM = 'https://custom.admixer.net/prebid.aspx'; const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; @@ -67,7 +67,7 @@ describe('AdmixerAdapter', function () { it('should add referrer and imp to be equal bidRequest', function () { const request = spec.buildRequests(validRequest, bidderRequest); - const payload = JSON.parse(request.data.substr(5)); + const payload = request.data; expect(payload.referrer).to.not.be.undefined; expect(payload.imps[0]).to.deep.equal(validRequest[0]); }); @@ -75,7 +75,7 @@ describe('AdmixerAdapter', function () { it('sends bid request to ENDPOINT via GET', function () { const request = spec.buildRequests(validRequest, bidderRequest); expect(request.url).to.equal(ENDPOINT_URL); - expect(request.method).to.equal('GET'); + expect(request.method).to.equal('POST'); }); it('sends bid request to CUSTOM_ENDPOINT via GET', function () { @@ -85,7 +85,7 @@ describe('AdmixerAdapter', function () { }); const request = config.runWithBidder(BIDDER_CODE, () => spec.buildRequests(validRequest, bidderRequest)); expect(request.url).to.equal(ENDPOINT_URL_CUSTOM); - expect(request.method).to.equal('GET'); + expect(request.method).to.equal('POST'); }); }); @@ -101,7 +101,7 @@ describe('AdmixerAdapter', function () { 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', 'ttl': 360, 'netRevenue': false, - 'bidId': '5e4e763b6bc60b', + 'requestId': '5e4e763b6bc60b', 'dealId': 'asd123', 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} }] @@ -112,13 +112,12 @@ describe('AdmixerAdapter', function () { const ads = response.body.ads; let expectedResponse = [ { - 'requestId': ads[0].bidId, + 'requestId': ads[0].requestId, 'cpm': ads[0].cpm, 'creativeId': ads[0].creativeId, 'width': ads[0].width, 'height': ads[0].height, 'ad': ads[0].ad, - 'vastUrl': undefined, 'currency': ads[0].currency, 'netRevenue': ads[0].netRevenue, 'ttl': ads[0].ttl, diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 62073fc6aaa..3b6ad8d0912 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -2,10 +2,26 @@ import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' import { spec } from 'modules/adnuntiusBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; +import { getStorageManager } from 'src/storageManager.js'; describe('adnuntiusBidAdapter', function () { + const URL = 'https://ads.adnuntius.delivery/i?tzo='; + const GVLID = 855; + const usi = utils.generateUUID() + const meta = [{ key: 'usi', value: usi }] + const storage = getStorageManager(GVLID, 'adnuntius') + storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) + + afterEach(function () { + config.resetConfig(); + }); const tzo = new Date().getTimezoneOffset(); - const ENDPOINT_URL = `https://delivery.adnuntius.com/i?tzo=${tzo}&format=json`; + const ENDPOINT_URL = `${URL}${tzo}&format=json&userId=${usi}`; + const ENDPOINT_URL_NOCOOKIE = `${URL}${tzo}&format=json&userId=${usi}&noCookies=true`; + const ENDPOINT_URL_SEGMENTS = `${URL}${tzo}&format=json&segments=segment1,segment2,segment3&userId=${usi}`; + const ENDPOINT_URL_CONSENT = `${URL}${tzo}&format=json&consentString=consentString&userId=${usi}`; const adapter = newBidder(spec); const bidRequests = [ @@ -114,7 +130,177 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url'); expect(request[0].url).to.equal(ENDPOINT_URL); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\",\"targetId\":\"123\"}]}'); + expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\",\"targetId\":\"123\"}],\"metaData\":{\"usi\":\"' + usi + '\"}}'); + }); + + it('should pass segments if available in config', function () { + config.setBidderConfig({ + bidders: ['adnuntius', 'other'], + config: { + ortb2: { + user: { + data: [{ + name: 'adnuntius', + segment: [{ id: 'segment1' }, { id: 'segment2' }] + }, + { + name: 'other', + segment: ['segment3'] + }], + } + } + } + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); + }); + + it('should skip segments in config if not either id or array of strings', function () { + config.setBidderConfig({ + bidders: ['adnuntius', 'other'], + config: { + ortb2: { + user: { + data: [{ + name: 'adnuntius', + segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] + }, + { + name: 'other', + segment: [{ + notright: 'segment4' + }] + }], + } + } + } + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); + }); + }); + + describe('user privacy', function () { + it('should send GDPR Consent data if gdprApplies', function () { + let request = spec.buildRequests(bidRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); + }); + + it('should not send GDPR Consent data if gdprApplies equals undefined', function () { + let request = spec.buildRequests(bidRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL); + }); + + it('should pass segments if available in config', function () { + config.setBidderConfig({ + bidders: ['adnuntius', 'other'], + config: { + ortb2: { + user: { + data: [{ + name: 'adnuntius', + segment: [{ id: 'segment1' }, { id: 'segment2' }] + }, + { + name: 'other', + segment: ['segment3'] + }], + } + } + } + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); + }); + + it('should skip segments in config if not either id or array of strings', function () { + config.setBidderConfig({ + bidders: ['adnuntius', 'other'], + config: { + ortb2: { + user: { + data: [{ + name: 'adnuntius', + segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] + }, + { + name: 'other', + segment: [{ + notright: 'segment4' + }] + }], + } + } + } + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); + }); + + it('should user user ID if present in ortb2.user.id field', function () { + config.setBidderConfig({ + bidders: ['adnuntius', 'other'], + config: { + ortb2: { + user: { + id: usi + } + } + } + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL); + }); + }); + + describe('user privacy', function () { + it('should send GDPR Consent data if gdprApplies', function () { + let request = spec.buildRequests(bidRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); + }); + + it('should not send GDPR Consent data if gdprApplies equals undefined', function () { + let request = spec.buildRequests(bidRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL); + }); + }); + + describe('use cookie', function () { + it('should send noCookie in url if set to false.', function () { + config.setBidderConfig({ + bidders: ['adnuntius'], + config: { + useCookie: false + } + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL_NOCOOKIE); }); }); diff --git a/test/spec/modules/adnuntiusRtdProvider_spec.js b/test/spec/modules/adnuntiusRtdProvider_spec.js new file mode 100644 index 00000000000..c1fcfbf298f --- /dev/null +++ b/test/spec/modules/adnuntiusRtdProvider_spec.js @@ -0,0 +1,145 @@ +import { adnuntiusSubmodule } from 'modules/adnuntiusRtdProvider.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +import { config as _config } from 'src/config.js'; + +const responseHeader = { 'Content-Type': 'application/json' }; + +describe('adnuntiusRtdProvider is a RTD provider that', function () { + describe('has a method `init` that', function () { + it('exists', function () { + expect(adnuntiusSubmodule.init).to.be.a('function'); + }); + it('returns false missing config params', function () { + const config = { + name: 'adnuntius', + waitForIt: true, + }; + const value = adnuntiusSubmodule.init(config); + expect(value).to.equal(false); + }); + it('returns false if missing providers param', function () { + const config = { + name: 'adnuntius', + waitForIt: true, + params: {} + }; + const value = adnuntiusSubmodule.init(config); + expect(value).to.equal(false); + }); + it('returns true if providers param included', function () { + const config = { + name: 'adnuntius', + waitForIt: true, + params: { + providers: [] + } + }; + const value = adnuntiusSubmodule.init(config); + expect(value).to.equal(true); + }); + }); + + describe('has a method `getBidRequestData` that', function () { + it('exists', function () { + expect(adnuntiusSubmodule.getBidRequestData).to.be.a('function'); + }); + it('verify config params', function () { + expect(config.name).to.not.be.undefined; + expect(config.name).to.equal('adnuntius'); + expect(config.params.providers).to.not.be.undefined; + expect(config.params).to.have.property('providers'); + expect(config.params.providers[0]).to.have.property('siteId') + expect(config.params.providers[0]).to.have.property('userId') + }); + + it('send correct request', function () { + const callback = sinon.spy(); + let request; + const adUnitsOriginal = adUnits; + adnuntiusSubmodule.getBidRequestData({ adUnits: adUnits }, callback, config); + request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(data)); + expect(request.url).to.be.include(`&s=site123&browserId=mike`); + expect(adUnits).to.length(2); + expect(adUnits[0]).to.be.eq(adUnitsOriginal[0]); + }); + }); + + describe('has a method `setGlobalConfig` that', function () { + it('exists', function () { + expect(adnuntiusSubmodule.setGlobalConfig).to.be.a('function'); + }); + + it('sets global config', function () { + adnuntiusSubmodule.setGlobalConfig(config, concatSegments); + const globalConfig = _config.getBidderConfig() + expect(globalConfig).to.have.property('adnuntius') + expect(globalConfig.adnuntius).to.have.property('ortb2') + expect(globalConfig.adnuntius.ortb2).to.have.property('user') + expect(globalConfig.adnuntius.ortb2.user).to.have.property('data') + expect(globalConfig.adnuntius.ortb2.user.data).to.be.a('array') + expect(globalConfig.adnuntius.ortb2.user.data[0]).to.have.property('name') + expect(globalConfig.adnuntius.ortb2.user.data[0].name).to.equal('adnuntius') + expect(globalConfig.adnuntius.ortb2.user.data[0]).to.have.property('segment') + expect(globalConfig.adnuntius.ortb2.user.data[0].segment).to.be.a('array') + expect(globalConfig.adnuntius.ortb2.user.data[0].segment[0]).to.have.property('id') + expect(globalConfig.adnuntius.ortb2.user.data[0].segment[0].id).to.equal('segment2') + }); + }); +}); + +const config = { + name: 'adnuntius', + waitForIt: true, + params: { + bidders: ['adnuntius'], + providers: [{ + siteId: 'site123', + userId: 'mike' + }] + } +}; + +const adUnits = [ + { + code: 'one-div-id', + mediaTypes: { + banner: { + sizes: [970, 250] + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 12345370, + } + }] + }, + { + code: 'two-div-id', + mediaTypes: { + banner: { sizes: [300, 250] } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 12345370, + } + }] + }]; + +const data = { + 'expiryEpochMillis': 1640165064285, + 'segments': [ + 'segment2', + 'segment1' + ] +}; + +const concatSegments = [ + { id: 'segment2' }, + { id: 'segment1' }, +] diff --git a/test/spec/modules/adoceanBidAdapter_spec.js b/test/spec/modules/adoceanBidAdapter_spec.js index 2b4b7d711e1..43316cd7483 100644 --- a/test/spec/modules/adoceanBidAdapter_spec.js +++ b/test/spec/modules/adoceanBidAdapter_spec.js @@ -150,7 +150,8 @@ describe('AdoceanAdapter', function () { 'width': '300', 'height': '250', 'crid': '0af345b42983cc4bc0', - 'ttl': '300' + 'ttl': '300', + 'adomain': ['adocean.pl'] } ], 'headers': { @@ -186,7 +187,10 @@ describe('AdoceanAdapter', function () { 'ad': '', 'creativeId': '0af345b42983cc4bc0', 'ttl': 300, - 'netRevenue': false + 'netRevenue': false, + 'meta': { + 'advertiserDomains': ['adocean.pl'] + } } ]; @@ -197,6 +201,8 @@ describe('AdoceanAdapter', function () { resultKeys.forEach(function(k) { if (k === 'ad') { expect(result[0][k]).to.match(/$/); + } else if (k === 'meta') { + expect(result[0][k]).to.deep.equal(expectedResponse[0][k]); } else { expect(result[0][k]).to.equal(expectedResponse[0][k]); } diff --git a/test/spec/modules/adomikAnalyticsAdapter_spec.js b/test/spec/modules/adomikAnalyticsAdapter_spec.js index 1414b2402b9..93822d04c88 100644 --- a/test/spec/modules/adomikAnalyticsAdapter_spec.js +++ b/test/spec/modules/adomikAnalyticsAdapter_spec.js @@ -1,5 +1,6 @@ import adomikAnalytics from 'modules/adomikAnalyticsAdapter.js'; -import {expect} from 'chai'; +import { expect } from 'chai'; + let events = require('src/events'); let adapterManager = require('src/adapterManager').default; let constants = require('src/constants.json'); @@ -8,6 +9,7 @@ describe('Adomik Prebid Analytic', function () { let sendEventStub; let sendWonEventStub; let clock; + before(function () { clock = sinon.useFakeTimers(); }); @@ -43,6 +45,8 @@ describe('Adomik Prebid Analytic', function () { const initOptions = { id: '123456', url: 'testurl', + testId: '12345', + testValue: '1000' }; const bid = { @@ -69,6 +73,9 @@ describe('Adomik Prebid Analytic', function () { expect(adomikAnalytics.currentContext).to.deep.equal({ uid: '123456', url: 'testurl', + sampling: undefined, + testId: '12345', + testValue: '1000', id: '', timeouted: false }); @@ -79,6 +86,9 @@ describe('Adomik Prebid Analytic', function () { expect(adomikAnalytics.currentContext).to.deep.equal({ uid: '123456', url: 'testurl', + sampling: undefined, + testId: '12345', + testValue: '1000', id: 'test-test-test', timeouted: false }); @@ -91,7 +101,7 @@ describe('Adomik Prebid Analytic', function () { type: 'request', event: { bidder: 'BIDDERTEST', - placementCode: 'placementtest', + placementCode: '0000', } }); diff --git a/test/spec/modules/adotBidAdapter_spec.js b/test/spec/modules/adotBidAdapter_spec.js index d580cd763a4..c180a5f79f7 100644 --- a/test/spec/modules/adotBidAdapter_spec.js +++ b/test/spec/modules/adotBidAdapter_spec.js @@ -1,3136 +1,333 @@ import { expect } from 'chai'; -import { executeRenderer } from 'src/Renderer.js'; -import * as utils from 'src/utils.js'; import { spec } from 'modules/adotBidAdapter.js'; const BIDDER_URL = 'https://dsp.adotmob.com/headerbidding/bidrequest'; describe('Adot Adapter', function () { - const examples = { - adUnit_banner: { - adUnitCode: 'ad_unit_banner', - bidder: 'adot', - bidderRequestId: 'bid_request_id', - bidId: 'bid_id', - params: {}, - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - } - }, - - adUnit_video_outstream: { - adUnitCode: 'ad_unit_video_outstream', - bidder: 'adot', - bidderRequestId: 'bid_request_id', - bidId: 'bid_id', - params: { - video: { - mimes: ['video/mp4'], - minDuration: 5, - maxDuration: 30, - protocols: [2, 3] - } - }, - mediaTypes: { - video: { - context: 'outstream', - playerSize: [[300, 250]] - } - } - }, - - adUnit_video_instream: { - adUnitCode: 'ad_unit_video_instream', - bidder: 'adot', - bidderRequestId: 'bid_request_id', - bidId: 'bid_id', - params: { - video: { - instreamContext: 'pre-roll', - mimes: ['video/mp4'], - minDuration: 5, - maxDuration: 30, - protocols: [2, 3] - } - }, - mediaTypes: { - video: { - context: 'instream', - playerSize: [[300, 250]] - } - } - }, - - adUnitContext: { - refererInfo: { - referer: 'https://we-are-adot.com/test', - }, - gdprConsent: { - consentString: 'consent_string', - gdprApplies: true - } - }, - - adUnit_position: { - adUnitCode: 'ad_unit_position', - bidder: 'adot', - bidderRequestId: 'bid_request_id', - bidId: 'bid_id', - params: { - position: 1 - }, - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - } - }, - - adUnit_native: { - adUnitCode: 'ad_unit_native', - bidder: 'adot', - bidderRequestId: 'bid_request_id', - bidId: 'bid_id', - params: {}, - mediaTypes: { - native: { - title: {required: true, len: 140}, - icon: {required: true, sizes: [50, 50]}, - image: {required: false, sizes: [320, 200]}, - sponsoredBy: {required: false}, - body: {required: false}, - cta: {required: true} - } - } - }, + describe('isBidRequestValid', function () { + it('should return false if video and !isValidVideo', function () { + const bid = { mediaTypes: { video: {} } }; + const isBidRequestValid = spec.isBidRequestValid(bid); + expect(isBidRequestValid).to.equal(false); + }) + + it('should return true if video and isValidVideo', function () { + const bid = { mediaTypes: { video: { 'mimes': 1, 'minduration': 1, 'maxduration': 1, 'protocols': 1 } } }; + const isBidRequestValid = spec.isBidRequestValid(bid); + expect(isBidRequestValid).to.equal(true); + }) + + it('should return true if !video', function () { + const bid = { mediaTypes: { banner: {} } }; + const isBidRequestValid = spec.isBidRequestValid(bid); + expect(isBidRequestValid).to.equal(true); + }) + }); - serverRequest_banner: { - method: 'POST', - url: 'https://we-are-adot.com/bidrequest', - data: { - id: 'bid_request_id', - imp: [ - { - id: 'imp_id_banner_0_0', - banner: { - format: [{ - w: 300, - h: 200 - }] - }, - video: null - } - ], + describe('buildRequests', function () { + it('should build request (banner)', function () { + const bidderRequestId = 'bidderRequestId'; + const validBidRequests = [{ bidderRequestId, mediaTypes: {} }, { bidderRequestId, bidId: 'bidId', mediaTypes: { banner: { sizes: [[300, 250]] } }, params: { placementId: 'placementId', adUnitCode: 200 } }]; + const bidderRequest = { position: 2, refererInfo: { referer: 'http://localhost.com' }, gdprConsent: { consentString: 'consentString', gdprApplies: true } }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const buildBidRequestResponse = { + id: bidderRequestId, + imp: [{ + id: validBidRequests[1].bidId, + ext: { + placementId: validBidRequests[1].params.placementId, + adUnitCode: validBidRequests[1].adUnitCode, + container: undefined + }, + banner: { + pos: bidderRequest.position, + format: [{ w: validBidRequests[1].mediaTypes.banner.sizes[0][0], h: validBidRequests[1].mediaTypes.banner.sizes[0][1] }] + }, + bidfloorcur: 'USD', + bidfloor: 0 + }], site: { - page: 'https://we-are-adot.com/test', - domain: 'we-are-adot.com', - name: 'we-are-adot.com' - }, - device: { - ua: '', - language: 'en' + page: bidderRequest.refererInfo.referer, + domain: 'localhost.com', + name: 'localhost.com', + publisher: { + // id: 'adot' + id: undefined + } }, - user: null, - regs: null, - at: 1, + device: { ua: navigator.userAgent, language: navigator.language }, + user: { ext: { consent: bidderRequest.gdprConsent.consentString } }, + regs: { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies } }, ext: { - adot: { - 'adapter_version': 'v1.0.0' - } - } - }, - _adot_internal: { - impressions: [ - { - impressionId: 'imp_id_banner_0_0', - adUnitCode: 'ad_unit_banner', - bidId: 'imp_id_banner' - } - ] + adot: { adapter_version: 'v2.0.0' }, + should_use_gzip: true + }, + at: 1 } - }, - serverRequest_banner_twoImps: { - method: 'POST', - url: 'https://we-are-adot.com/bidrequest', - data: { - id: 'bid_request_id', - imp: [ - { - id: 'imp_id_banner_0_0', - banner: { - format: [{ - w: 300, - h: 200 - }] - }, - video: null + expect(request).to.deep.equal([{ + method: 'POST', + url: BIDDER_URL, + data: buildBidRequestResponse + }]) + }) + + it('should build request (native)', function () { + const bidderRequestId = 'bidderRequestId'; + const validBidRequests = [{ bidderRequestId, mediaTypes: {} }, { bidderRequestId, bidId: 'bidId', mediaTypes: { native: { title: { required: true, len: 50, sizes: [[300, 250]] }, wrong: {}, image: {} } }, params: { placementId: 'placementId', adUnitCode: 200 } }]; + const bidderRequest = { position: 2, refererInfo: { referer: 'http://localhost.com' }, gdprConsent: { consentString: 'consentString', gdprApplies: true } }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const buildBidRequestResponse = { + id: bidderRequestId, + imp: [{ + id: validBidRequests[1].bidId, + ext: { + placementId: validBidRequests[1].params.placementId, + adUnitCode: validBidRequests[1].adUnitCode, + container: undefined }, - { - id: 'imp_id_banner_2_0_0', - banner: { - format: [{ - w: 300, - h: 200 - }] - }, - video: null - } - ], - site: { - page: 'https://we-are-adot.com/test', - domain: 'we-are-adot.com', - name: 'we-are-adot.com' - }, - device: { - ua: '', - language: 'en' - }, - user: null, - regs: null, - at: 1, - ext: { - adot: { - 'adapter_version': 'v1.0.0' - } - } - }, - _adot_internal: { - impressions: [ - { - impressionId: 'imp_id_banner_0_0', - adUnitCode: 'ad_unit_banner', - bidId: 'imp_id_banner' + native: { + request: '{\"assets\":[{\"id\":1,\"required\":true,\"title\":{\"len\":50,\"wmin\":300,\"hmin\":250}},{\"id\":3,\"img\":{\"type\":3}}]}' }, - { - impressionId: 'imp_id_banner_2_0_0', - adUnitCode: 'ad_unit_banner_2', - bidId: 'imp_id_banner_2' - } - ] - } - }, - - serverRequest_video_instream: { - method: 'POST', - url: 'https://we-are-adot.com/bidrequest', - data: { - id: 'bid_request_id', - imp: [ - { - id: 'imp_id_video_instream_0', - banner: null, - video: { - mimes: ['video/mp4'], - w: 300, - h: 200, - startdelay: 0, - minduration: 5, - maxduration: 35, - protocols: [2, 3] - } - } - ], + bidfloorcur: 'USD', + bidfloor: 0 + }], site: { - page: 'https://we-are-adot.com/test', - domain: 'we-are-adot.com', - name: 'we-are-adot.com' - }, - device: { - ua: '', - language: 'en' - }, - user: null, - regs: null, - at: 1, - ext: { - adot: { - 'adapter_version': 'v1.0.0' - } - } - }, - _adot_internal: { - impressions: [ - { - impressionId: 'imp_id_video_instream_0', - adUnitCode: 'ad_unit_video_instream', - bidId: 'imp_id_video_instream' - } - ] - } - }, - - serverRequest_video_outstream: { - method: 'POST', - url: 'https://we-are-adot.com/bidrequest', - data: { - id: 'bid_request_id', - imp: [ - { - id: 'imp_id_video_outstream_0', - banner: null, - video: { - mimes: ['video/mp4'], - w: 300, - h: 200, - startdelay: null, - minduration: 5, - maxduration: 35, - protocols: [2, 3] - } + page: bidderRequest.refererInfo.referer, + domain: 'localhost.com', + name: 'localhost.com', + publisher: { + // id: 'adot' + id: undefined } - ], - site: { - page: 'https://we-are-adot.com/test', - domain: 'we-are-adot.com', - name: 'we-are-adot.com' - }, - device: { - ua: '', - language: 'en' }, - user: null, - regs: null, - at: 1, + device: { ua: navigator.userAgent, language: navigator.language }, + user: { ext: { consent: bidderRequest.gdprConsent.consentString } }, + regs: { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies } }, ext: { - adot: { - 'adapter_version': 'v1.0.0' - } - } - }, - _adot_internal: { - impressions: [ - { - impressionId: 'imp_id_video_outstream_0', - adUnitCode: 'ad_unit_video_outstream', - bidId: 'imp_id_video_outstream' - } - ] + adot: { adapter_version: 'v2.0.0' }, + should_use_gzip: true + }, + at: 1 } - }, - serverRequest_video_instream_outstream: { - method: 'POST', - url: 'https://we-are-adot.com/bidrequest', - data: { - id: 'bid_request_id', - imp: [ - { - id: 'imp_id_video_instream_0', - banner: null, - video: { - mimes: ['video/mp4'], - w: 300, - h: 200, - startdelay: 0, - minduration: 5, - maxduration: 35, - protocols: [2, 3] - } + expect(request).to.deep.equal([{ + method: 'POST', + url: BIDDER_URL, + data: buildBidRequestResponse + }]) + }) + + it('should build request (video)', function () { + const bidderRequestId = 'bidderRequestId'; + const validBidRequests = [{ bidderRequestId, mediaTypes: {} }, { bidderRequestId, bidId: 'bidId', mediaTypes: { video: { playerSize: [[300, 250]], minduration: 1, maxduration: 2, api: 'api', linearity: 'linearity', mimes: [], placement: 'placement', playbackmethod: 'playbackmethod', protocols: 'protocol', startdelay: 'startdelay' } }, params: { placementId: 'placementId', adUnitCode: 200 } }]; + const bidderRequest = { position: 2, refererInfo: { referer: 'http://localhost.com' }, gdprConsent: { consentString: 'consentString', gdprApplies: true } }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const buildBidRequestResponse = { + id: bidderRequestId, + imp: [{ + id: validBidRequests[1].bidId, + ext: { + placementId: validBidRequests[1].params.placementId, + adUnitCode: validBidRequests[1].adUnitCode, + container: undefined }, - { - id: 'imp_id_video_outstream_0', - banner: null, - video: { - mimes: ['video/mp4'], - w: 300, - h: 200, - startdelay: null, - minduration: 5, - maxduration: 35, - protocols: [2, 3] - } - } - ], - site: { - page: 'https://we-are-adot.com/test', - domain: 'we-are-adot.com', - name: 'we-are-adot.com' - }, - device: { - ua: '', - language: 'en' - }, - user: null, - regs: null, - at: 1, - ext: { - adot: { - 'adapter_version': 'v1.0.0' - } - } - }, - _adot_internal: { - impressions: [ - { - impressionId: 'imp_id_video_instream_0', - adUnitCode: 'ad_unit_video_instream', - bidId: 'imp_id_video_instream' + video: { + api: 'api', + h: 250, + linearity: 'linearity', + maxduration: 2, + mimes: [], + minduration: 1, + placement: 'placement', + playbackmethod: 'playbackmethod', + pos: 0, + protocols: 'protocol', + skip: 0, + startdelay: 'startdelay', + w: 300 }, - { - impressionId: 'imp_id_video_outstream_0', - adUnitCode: 'ad_unit_video_outstream', - bidId: 'imp_id_video_outstream' - } - ] - } - }, - - serverRequest_position: { - method: 'POST', - url: 'https://we-are-adot.com/bidrequest', - data: { - id: 'bid_request_id', - imp: [ - { - id: 'imp_id_banner', - banner: { - format: [{ - w: 300, - h: 200 - }], - position: 1 - }, - video: null - } - ], + bidfloorcur: 'USD', + bidfloor: 0 + }], site: { - page: 'https://we-are-adot.com/test', - domain: 'we-are-adot.com', - name: 'we-are-adot.com' - }, - device: { - ua: '', - language: 'en' - }, - user: null, - regs: null, - at: 1, - ext: { - adot: { - 'adapter_version': 'v1.0.0' - } - } - }, - _adot_internal: { - impressions: [ - { - impressionId: 'imp_id_banner', - adUnitCode: 'ad_unit_position' + page: bidderRequest.refererInfo.referer, + domain: 'localhost.com', + name: 'localhost.com', + publisher: { + // id: 'adot' + id: undefined } - ] - } - }, - - serverRequest_native: { - method: 'POST', - url: 'https://we-are-adot.com/bidrequest', - data: { - id: 'bid_request_id', - imp: [ - { - id: 'imp_id_native_0', - native: { - request: { - assets: [ - { - id: 1, - required: true, - title: { - len: 140 - } - }, - { - id: 2, - required: true, - img: { - type: 1, - wmin: 50, - hmin: 50 - } - }, - { - id: 3, - required: false, - img: { - type: 3, - wmin: 320, - hmin: 200 - } - }, - { - id: 4, - required: false, - data: { - type: 1 - } - }, - { - id: 5, - required: false, - data: { - type: 2 - } - }, - { - id: 6, - required: true, - data: { - type: 12 - } - } - ] - } - }, - video: null, - banner: null - } - ], - site: { - page: 'https://we-are-adot.com/test', - domain: 'we-are-adot.com', - name: 'we-are-adot.com' }, - device: { - ua: '', - language: 'en' - }, - user: null, - regs: null, - at: 1, + device: { ua: navigator.userAgent, language: navigator.language }, + user: { ext: { consent: bidderRequest.gdprConsent.consentString } }, + regs: { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies } }, ext: { - adot: { - 'adapter_version': 'v1.0.0' - } - } - }, - _adot_internal: { - impressions: [ - { - impressionId: 'imp_id_native_0', - adUnitCode: 'ad_unit_native', - bidId: 'imp_id_native' - } - ] - } - }, - - serverResponse_banner: { - body: { - cur: 'EUR', - seatbid: [ - { - bid: [ - { - impid: 'imp_id_banner_0_0', - crid: 'creative_id', - adm: 'creative_data_${AUCTION_PRICE}', - nurl: 'win_notice_url_${AUCTION_PRICE}', - price: 1.5, - h: 350, - w: 300, - ext: { - adot: { - media_type: 'banner' - } - } - } - ] - } - ] - } - }, - - serverResponse_banner_twoBids: { - body: { - cur: 'EUR', - seatbid: [ - { - bid: [ - { - impid: 'imp_id_banner_0_0', - crid: 'creative_id', - adm: 'creative_data_${AUCTION_PRICE}', - nurl: 'win_notice_url_${AUCTION_PRICE}', - price: 1.5, - h: 350, - w: 300, - ext: { - adot: { - media_type: 'banner' - } - } - }, - { - impid: 'imp_id_banner_2_0_0', - crid: 'creative_id_2', - adm: 'creative_data_2_${AUCTION_PRICE}', - nurl: 'win_notice_url_2_${AUCTION_PRICE}', - price: 2.5, - h: 400, - w: 350, - ext: { - adot: { - media_type: 'banner' - } - } - } - ] - } - ] + adot: { adapter_version: 'v2.0.0' }, + should_use_gzip: true + }, + at: 1 } - }, - serverResponse_video_instream: { - body: { - cur: 'EUR', - seatbid: [ - { - bid: [ - { - impid: 'imp_id_video_instream_0', - crid: 'creative_id', - adm: 'creative_data_${AUCTION_PRICE}', - nurl: 'win_notice_url_${AUCTION_PRICE}', - price: 1.5, - ext: { - adot: { - media_type: 'video' - } - } - } - ] - } - ] - } - }, + expect(request).to.deep.equal([{ + method: 'POST', + url: BIDDER_URL, + data: buildBidRequestResponse + }]) + }) + }); - serverResponse_video_outstream: { - body: { - cur: 'EUR', - seatbid: [ - { - bid: [ - { - impid: 'imp_id_video_outstream_0', - crid: 'creative_id', - adm: 'creative_data_${AUCTION_PRICE}', - nurl: 'win_notice_url_${AUCTION_PRICE}', - price: 1.5, - ext: { - adot: { - media_type: 'video' - } - } - } - ] - } - ] + describe('interpretResponse', function () { + it('should return [] if !isValidResponse', function () { + const serverResponse = 'response'; + const request = 'request'; + const interpretedResponse = spec.interpretResponse(serverResponse, request); + expect(interpretedResponse).to.deep.equal([]); + }) + + it('should return [] if !isValidRequest', function () { + const serverResponse = { body: { cur: 'EUR', seatbid: [] } }; + const request = 'request'; + const interpretedResponse = spec.interpretResponse(serverResponse, request); + expect(interpretedResponse).to.deep.equal([]); + }) + + it('should return bidResponse with random media type', function () { + const impId = 'impId'; + const bid = { adm: 'adm', impid: impId, price: 2, crid: 'crid', dealid: 'dealid', adomain: 'adomain', ext: { adot: { media_type: 'media_type', size: { w: 300, h: 250 } } } } + const serverResponse = { body: { cur: 'EUR', seatbid: [{ bid: {} }, { bid: [bid] }] } }; + const request = { data: { imp: [{ id: impId }] } }; + const bidResponse = { + requestId: impId, + cpm: bid.price, + currency: serverResponse.body.cur, + ttl: 10, + creativeId: bid.crid, + netRevenue: true, + mediaType: bid.ext.adot.media_type, + dealId: bid.dealid, + meta: { advertiserDomains: bid.adomain }, + width: bid.ext.adot.size.w, + height: bid.ext.adot.size.h, + ad: bid.adm, + adUrl: null, + vastXml: null, + vastUrl: null, + renderer: null } - }, - serverResponse_video_instream_outstream: { - body: { - cur: 'EUR', - seatbid: [ - { - bid: [ - { - impid: 'imp_id_video_instream_0', - crid: 'creative_id', - adm: 'creative_data_${AUCTION_PRICE}', - nurl: 'win_notice_url_${AUCTION_PRICE}', - price: 1.5, - ext: { - adot: { - media_type: 'video' - } - } - }, - { - impid: 'imp_id_video_outstream_0', - crid: 'creative_id', - adm: 'creative_data_${AUCTION_PRICE}', - nurl: 'win_notice_url_${AUCTION_PRICE}', - price: 1.5, - ext: { - adot: { - media_type: 'video' - } - } - } - ] - } - ] + const interpretedResponse = spec.interpretResponse(serverResponse, request); + expect(interpretedResponse).to.deep.equal([bidResponse]); + }) + + it('should return bidResponse with native', function () { + const impId = 'impId'; + const bid = { adm: '{"native":{"assets":[{"id":1,"title":{"text":"title"}},{"id":3,"img":{"url":"url","w":300,"h":250}}],"link":{"url":"clickUrl","clicktrackers":"clicktrackers"},"imptrackers":["imptracker"],"jstracker":"jstracker"}}', impid: impId, price: 2, crid: 'crid', dealid: 'dealid', adomain: 'adomain', ext: { adot: { media_type: 'native', size: { width: 300, height: 250 } } } } + const serverResponse = { body: { cur: 'EUR', seatbid: [{ bid: {} }, { bid: [bid] }] } }; + const request = { data: { imp: [{ id: impId }] } }; + const bidResponse = { + requestId: impId, + cpm: bid.price, + currency: serverResponse.body.cur, + ttl: 10, + creativeId: bid.crid, + netRevenue: true, + mediaType: bid.ext.adot.media_type, + dealId: bid.dealid, + meta: { advertiserDomains: bid.adomain }, + native: { + title: 'title', + image: { url: 'url', width: 300, height: 250 }, + clickUrl: 'clickUrl', + clickTrackers: 'clicktrackers', + impressionTrackers: ['imptracker'], + javascriptTrackers: ['jstracker'] + } } - }, - serverResponse_native: { - body: { - cur: 'EUR', - seatbid: [ - { - bid: [ - { - impid: 'imp_id_native_0', - crid: 'creative_id', - adm: '{"native":{"assets":[{"id":1,"title":{"len":140,"text":"Hi everyone"}},{"id":2,"img":{"url":"https://adotmob.com","type":1,"w":50,"h":50}},{"id":3,"img":{"url":"https://adotmob.com","type":3,"w":320,"h":200}},{"id":4,"data":{"type":1,"value":"adotmob"}},{"id":5,"data":{"type":2,"value":"This is a test ad"}},{"id":6,"data":{"type":12,"value":"Click to buy"}}],"link":{"url":"https://adotmob.com?auction=${AUCTION_PRICE}"}}}', - nurl: 'win_notice_url_${AUCTION_PRICE}', - price: 1.5, - ext: { - adot: { - media_type: 'native' - } - } - } - ] - } - ] + const interpretedResponse = spec.interpretResponse(serverResponse, request); + expect(interpretedResponse).to.deep.equal([bidResponse]); + }) + + it('should return bidResponse with video', function () { + const impId = 'impId'; + const bid = { nurl: 'nurl', impid: impId, price: 2, crid: 'crid', dealid: 'dealid', adomain: 'adomain', ext: { adot: { media_type: 'video', size: { w: 300, h: 250 }, container: {}, adUnitCode: 20, video: { type: 'outstream' } } } } + const serverResponse = { body: { cur: 'EUR', seatbid: [{ bid: {} }, { bid: [bid] }] } }; + const request = { data: { imp: [{ id: impId }] } }; + const bidResponse = { + requestId: impId, + cpm: bid.price, + currency: serverResponse.body.cur, + ttl: 10, + creativeId: bid.crid, + netRevenue: true, + mediaType: bid.ext.adot.media_type, + dealId: bid.dealid, + meta: { advertiserDomains: bid.adomain }, + w: bid.ext.adot.size.w, + h: bid.ext.adot.size.h, + ad: null, + adUrl: bid.nurl, + vastXml: null, + vastUrl: bid.nurl } - } - }; - - describe('isBidRequestValid', function () { - describe('General', function () { - it('should return false when not given an ad unit', function () { - const adUnit = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an invalid ad unit', function () { - const adUnit = 'bad_bid'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit without bidder code', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.bidder = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with a bad bidder code', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.bidder = 'unknownBidder'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit without ad unit code', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.adUnitCode = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid ad unit code', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.adUnitCode = {}; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit without bid request identifier', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.bidderRequestId = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid bid request identifier', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.bidderRequestId = {}; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit without impression identifier', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.bidId = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid impression identifier', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.bidId = {}; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit without media types', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with empty media types', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes = {}; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with invalid media types', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes = 'bad_media_types'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - }); - - describe('Banner', function () { - it('should return true when given a valid ad unit', function () { - const adUnit = examples.adUnit_banner; - - expect(spec.isBidRequestValid(adUnit)).to.equal(true); - }); - - it('should return true when given a valid ad unit without bidder parameters', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.params = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(true); - }); - - it('should return false when given an ad unit without size', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes.banner.sizes = undefined; - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid size', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes.banner.sizes = 'bad_banner_size'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an empty size', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes.banner.sizes = []; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid size value', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes.banner.sizes = ['bad_banner_size_value']; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with a size value with less than 2 dimensions', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes.banner.sizes = [[300]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with a size value with more than 2 dimensions', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes.banner.sizes = [[300, 250, 30]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with a negative width value', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes.banner.sizes = [[-300, 250]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with a negative height value', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes.banner.sizes = [[300, -250]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid width value', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes.banner.sizes = [[false, 250]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid height value', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes.banner.sizes = [[300, {}]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - }); - - describe('Video', function () { - it('should return true when given a valid outstream ad unit', function () { - const adUnit = examples.adUnit_video_outstream; - - expect(spec.isBidRequestValid(adUnit)).to.equal(true); - }); - - it('should return true when given a valid pre-roll instream ad unit', function () { - const adUnit = utils.deepClone(examples.adUnit_video_instream); - adUnit.params.video.instreamContext = 'pre-roll'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(true); - }); - - it('should return true when given a valid mid-roll instream ad unit', function () { - const adUnit = utils.deepClone(examples.adUnit_video_instream); - adUnit.params.video.instreamContext = 'mid-roll'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(true); - }); - - it('should return true when given a valid post-roll instream ad unit', function () { - const adUnit = utils.deepClone(examples.adUnit_video_instream); - adUnit.params.video.instreamContext = 'post-roll'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(true); - }); - - it('should return true when given an ad unit without size', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(true); - }); - - it('should return true when given an ad unit with an empty size', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = []; - - expect(spec.isBidRequestValid(adUnit)).to.equal(true); - }); - - it('should return true when given an ad unit without minimum duration parameter', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.minDuration = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(true); - }); - - it('should return true when given an ad unit without maximum duration parameter', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.maxDuration = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(true); - }); - - it('should return false when given an ad unit without bidder parameters', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with invalid bidder parameters', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params = 'bad_bidder_parameters'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit without video parameters', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with invalid video parameters', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video = 'bad_bidder_parameters'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit without mime types parameter', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.mimes = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid mime types parameter', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.mimes = 'bad_mime_types'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an empty mime types parameter', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.mimes = []; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid mime types parameter value', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.mimes = [200]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid minimum duration parameter', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.minDuration = 'bad_min_duration'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid maximum duration parameter', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.maxDuration = 'bad_max_duration'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit without protocols parameter', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.protocols = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid protocols parameter', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.protocols = 'bad_protocols'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an empty protocols parameter', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.protocols = []; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid protocols parameter value', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.protocols = ['bad_protocols_value']; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an instream ad unit without instream context', function () { - const adUnit = utils.deepClone(examples.adUnit_video_instream); - adUnit.params.video.instreamContext = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an instream ad unit with an invalid instream context', function () { - const adUnit = utils.deepClone(examples.adUnit_video_instream); - adUnit.params.video.instreamContext = 'bad_instream_context'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit without context', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.context = undefined; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid context', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.context = []; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an adpod ad unit', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.context = 'adpod'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an unknown context', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.context = 'invalid_context'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid size', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = 'bad_video_size'; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid size value', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = ['bad_video_size_value']; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with a size value with less than 2 dimensions', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = [[300]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with a size value with more than 2 dimensions', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = [[300, 250, 30]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with a negative width value', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = [[-300, 250]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with a negative height value', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = [[300, -250]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid width value', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = [[false, 250]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - - it('should return false when given an ad unit with an invalid height value', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = [[300, {}]]; - - expect(spec.isBidRequestValid(adUnit)).to.equal(false); - }); - }); + const interpretedResponse = spec.interpretResponse(serverResponse, request); + expect(interpretedResponse).to.be.an('array').and.to.have.lengthOf(1) + expect(interpretedResponse[0].requestId).to.deep.equal(bidResponse.requestId); + expect(interpretedResponse[0].cpm).to.deep.equal(bidResponse.cpm); + expect(interpretedResponse[0].currency).to.deep.equal(bidResponse.currency); + expect(interpretedResponse[0].ttl).to.deep.equal(bidResponse.ttl); + expect(interpretedResponse[0].creativeId).to.deep.equal(bidResponse.creativeId); + expect(interpretedResponse[0].netRevenue).to.deep.equal(bidResponse.netRevenue); + expect(interpretedResponse[0].mediaType).to.deep.equal(bidResponse.mediaType); + expect(interpretedResponse[0].dealId).to.deep.equal(bidResponse.dealId); + expect(interpretedResponse[0].meta).to.deep.equal(bidResponse.meta); + expect(interpretedResponse[0].width).to.deep.equal(bidResponse.w); + expect(interpretedResponse[0].height).to.deep.equal(bidResponse.h); + expect(interpretedResponse[0].ad).to.deep.equal(bidResponse.ad); + expect(interpretedResponse[0].adUrl).to.deep.equal(bidResponse.adUrl); + expect(interpretedResponse[0].vastXml).to.deep.equal(bidResponse.vastXml); + expect(interpretedResponse[0].vastUrl).to.deep.equal(bidResponse.vastUrl); + expect(interpretedResponse[0].renderer).to.be.an('object'); + }) }); - describe('buildRequests', function () { - describe('ServerRequest', function () { - it('should return a server request when given a valid ad unit and a valid ad unit context', function () { - const adUnits = [examples.adUnit_banner]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.length(1); - expect(serverRequests[0].method).to.exist.and.to.be.a('string').and.to.equal('POST'); - expect(serverRequests[0].url).to.exist.and.to.be.a('string').and.to.equal(BIDDER_URL); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0]._adot_internal).to.exist.and.to.be.an('object'); - expect(serverRequests[0]._adot_internal.impressions).to.exist.and.to.be.an('array').and.to.have.length(1); - expect(serverRequests[0]._adot_internal.impressions[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0]._adot_internal.impressions[0].impressionId).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); - expect(serverRequests[0]._adot_internal.impressions[0].adUnitCode).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].adUnitCode); - }); - - it('should return a server request containing a position when given a valid ad unit and a valid ad unit context and a position in the bidder params', function () { - const adUnits = [examples.adUnit_position]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.length(1); - expect(serverRequests[0].method).to.exist.and.to.be.a('string').and.to.equal('POST'); - expect(serverRequests[0].url).to.exist.and.to.be.a('string').and.to.equal(BIDDER_URL); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0]._adot_internal).to.exist.and.to.be.an('object'); - expect(serverRequests[0]._adot_internal.impressions).to.exist.and.to.be.an('array').and.to.have.length(1); - expect(serverRequests[0]._adot_internal.impressions[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0]._adot_internal.impressions[0].impressionId).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); - expect(serverRequests[0]._adot_internal.impressions[0].adUnitCode).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].adUnitCode); - expect(serverRequests[0].data.imp[0].banner.pos).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.position); - }); - - it('should return a server request when given two valid ad units and a valid ad unit context', function () { - const adUnits_1 = utils.deepClone(examples.adUnit_banner); - adUnits_1.bidId = 'bid_id_1'; - adUnits_1.adUnitCode = 'ad_unit_banner_1'; - - const adUnits_2 = utils.deepClone(examples.adUnit_banner); - adUnits_2.bidId = 'bid_id_2'; - adUnits_2.adUnitCode = 'ad_unit_banner_2'; - - const adUnits = [adUnits_1, adUnits_2]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.length(1); - expect(serverRequests[0].method).to.exist.and.to.be.a('string').and.to.equal('POST'); - expect(serverRequests[0].url).to.exist.and.to.be.a('string').and.to.equal(BIDDER_URL); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0]._adot_internal).to.exist.and.to.be.an('object'); - expect(serverRequests[0]._adot_internal.impressions).to.exist.and.to.be.an('array').and.to.have.length(2); - expect(serverRequests[0]._adot_internal.impressions[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0]._adot_internal.impressions[0].impressionId).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); - expect(serverRequests[0]._adot_internal.impressions[0].adUnitCode).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].adUnitCode); - expect(serverRequests[0]._adot_internal.impressions[1]).to.exist.and.to.be.an('object'); - expect(serverRequests[0]._adot_internal.impressions[1].impressionId).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0_0`); - expect(serverRequests[0]._adot_internal.impressions[1].adUnitCode).to.exist.and.to.be.a('string').and.to.equal(adUnits[1].adUnitCode); - }); - - it('should return an empty server request list when given an empty ad unit list and a valid ad unit context', function () { - const adUnits = []; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.length(0); - }); - - it('should not return a server request when given no ad unit and a valid ad unit context', function () { - const serverRequests = spec.buildRequests(null, examples.adUnitContext); - - expect(serverRequests).to.equal(null); - }); - - it('should not return a server request when given a valid ad unit and no ad unit context', function () { - const adUnits = [examples.adUnit_banner]; - - const serverRequests = spec.buildRequests(adUnits, null); - - expect(serverRequests).to.be.an('array').and.to.have.length(1); - }); - - it('should not return a server request when given a valid ad unit and an invalid ad unit context', function () { - const adUnits = [examples.adUnit_banner]; - - const serverRequests = spec.buildRequests(adUnits, {}); - - expect(serverRequests).to.be.an('array').and.to.have.length(1); - }); - }); - - describe('BidRequest', function () { - it('should return a valid server request when given a valid ad unit', function () { - const adUnits = [examples.adUnit_banner]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.at).to.exist.and.to.be.a('number').and.to.equal(1); - expect(serverRequests[0].data.ext).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.ext.adot).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.ext.adot.adapter_version).to.exist.and.to.be.a('string').and.to.equal('v1.0.0'); - }); - - it('should return one server request when given one valid ad unit', function () { - const adUnits = [examples.adUnit_banner]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].bidderRequestId); - }); - - it('should return one server request when given two valid ad units with different impression identifiers', function () { - const adUnit_1 = utils.deepClone(examples.adUnit_banner); - adUnit_1.bidId = 'bid_id_1'; - - const adUnit_2 = utils.deepClone(examples.adUnit_banner); - adUnit_2.bidId = 'bid_id_2'; - - const adUnits = [adUnit_1, adUnit_2]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[1].bidderRequestId); - }); - - it('should return two server requests when given two valid ad units with different bid request identifiers', function () { - const adUnit_1 = utils.deepClone(examples.adUnit_banner); - adUnit_1.bidderRequestId = 'bidder_request_id_1'; - - const adUnit_2 = utils.deepClone(examples.adUnit_banner); - adUnit_2.bidderRequestId = 'bidder_request_id_2'; - - const adUnits = [adUnit_1, adUnit_2]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(2); - expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[1].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[1].bidderRequestId); - }); - }); - - describe('Impression', function () { - describe('Banner', function () { - it('should return a server request with one impression when given a valid ad unit', function () { - const adUnits = [examples.adUnit_banner]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); - expect(serverRequests[0].data.imp[0].banner).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][0]); - expect(serverRequests[0].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][1]); - expect(serverRequests[0].data.imp[0].banner.format).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ - w: adUnits[0].mediaTypes.banner.sizes[0][0], - h: adUnits[0].mediaTypes.banner.sizes[0][1] - }); - }); - - it('should return a server request with two impressions containing one banner formats when given a valid ad unit with two banner sizes', function () { - const adUnit = utils.deepClone(examples.adUnit_banner); - adUnit.mediaTypes.banner.sizes = [ - [300, 250], - [350, 300] - ]; - - const adUnits = [adUnit]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(2); - - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); - expect(serverRequests[0].data.imp[0].banner).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][0]); - expect(serverRequests[0].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][1]); - expect(serverRequests[0].data.imp[0].banner.format).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ - w: adUnits[0].mediaTypes.banner.sizes[0][0], - h: adUnits[0].mediaTypes.banner.sizes[0][1] - }); - - expect(serverRequests[0].data.imp[1]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[1].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_1`); - expect(serverRequests[0].data.imp[1].banner).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[1].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[1][0]); - expect(serverRequests[0].data.imp[1].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[1][1]); - expect(serverRequests[0].data.imp[1].banner.format).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[1].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ - w: adUnits[0].mediaTypes.banner.sizes[1][0], - h: adUnits[0].mediaTypes.banner.sizes[1][1] - }); - }); - - it('should return a server request with two impressions when given two valid ad units with different impression identifiers', function () { - const adUnit_1 = utils.deepClone(examples.adUnit_banner); - adUnit_1.bidId = 'bid_id_1'; - - const adUnit_2 = utils.deepClone(examples.adUnit_banner); - adUnit_2.bidId = 'bid_id_2'; - - const adUnits = [adUnit_1, adUnit_2]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(2); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); - expect(serverRequests[0].data.imp[0].banner).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][0]); - expect(serverRequests[0].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][1]); - expect(serverRequests[0].data.imp[0].banner.format).to.exist.and.to.be.an('array'); - expect(serverRequests[0].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ - w: adUnits[0].mediaTypes.banner.sizes[0][0], - h: adUnits[0].mediaTypes.banner.sizes[0][1] - }); - expect(serverRequests[0].data.imp[1]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[1].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0_0`); - expect(serverRequests[0].data.imp[1].banner).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[1].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][0]); - expect(serverRequests[0].data.imp[1].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][1]); - expect(serverRequests[0].data.imp[1].banner.format).to.exist.and.to.be.an('array'); - expect(serverRequests[0].data.imp[1].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ - w: adUnits[1].mediaTypes.banner.sizes[0][0], - h: adUnits[1].mediaTypes.banner.sizes[0][1] - }); - }); - - it('should return a server request with one overriden impression when given two valid ad units with identical identifiers', function () { - const adUnit_1 = utils.deepClone(examples.adUnit_banner); - adUnit_1.mediaTypes.banner.sizes = [[300, 250]]; - - const adUnit_2 = utils.deepClone(examples.adUnit_banner); - adUnit_2.mediaTypes.banner.sizes = [[350, 300]]; - - const adUnits = [adUnit_1, adUnit_2]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0_0`); - expect(serverRequests[0].data.imp[0].banner).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][0]); - expect(serverRequests[0].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][1]); - expect(serverRequests[0].data.imp[0].banner.format).to.exist.and.to.be.an('array'); - expect(serverRequests[0].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ - w: adUnits[1].mediaTypes.banner.sizes[0][0], - h: adUnits[1].mediaTypes.banner.sizes[0][1] - }); - }); - - it('should return two server requests with one impression when given two valid ad units with different bid request identifiers', function () { - const adUnit_1 = utils.deepClone(examples.adUnit_banner); - adUnit_1.bidderRequestId = 'bidder_request_id_1'; - - const adUnit_2 = utils.deepClone(examples.adUnit_banner); - adUnit_2.bidderRequestId = 'bidder_request_id_2'; - - const adUnits = [adUnit_1, adUnit_2]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(2); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0_0`); - expect(serverRequests[0].data.imp[0].banner).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][0]); - expect(serverRequests[0].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][1]); - expect(serverRequests[0].data.imp[0].banner.format).to.exist.and.to.be.an('array'); - expect(serverRequests[0].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ - w: adUnits[0].mediaTypes.banner.sizes[0][0], - h: adUnits[0].mediaTypes.banner.sizes[0][1] - }); - expect(serverRequests[1].data).to.exist.and.to.be.an('object'); - expect(serverRequests[1].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[1].bidderRequestId); - expect(serverRequests[1].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[1].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[1].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0_0`); - expect(serverRequests[1].data.imp[0].banner).to.exist.and.to.be.an('object'); - expect(serverRequests[1].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][0]); - expect(serverRequests[1].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.banner.sizes[0][1]); - expect(serverRequests[1].data.imp[0].banner.format).to.exist.and.to.be.an('array'); - expect(serverRequests[1].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ - w: adUnits[1].mediaTypes.banner.sizes[0][0], - h: adUnits[1].mediaTypes.banner.sizes[0][1] - }); - }); - }); - - describe('Video', function () { - it('should return a server request with one impression when given a valid outstream ad unit', function () { - const adUnit = examples.adUnit_video_outstream; - - const adUnits = [adUnit]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); - expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); - expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); - }); - - it('should return a server request with one impression when given a valid pre-roll instream ad unit', function () { - const adUnit = utils.deepClone(examples.adUnit_video_instream); - adUnit.params.video.instreamContext = 'pre-roll'; - - const adUnits = [adUnit]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.exist.and.to.be.a('number').and.to.equal(0); - expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); - expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); - }); - - it('should return a server request with one impression when given a valid mid-roll instream ad unit', function () { - const adUnit = utils.deepClone(examples.adUnit_video_instream); - adUnit.params.video.instreamContext = 'mid-roll'; - - const adUnits = [adUnit]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.exist.and.to.be.a('number').and.to.equal(-1); - expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); - expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); - }); - - it('should return a server request with one impression when given a valid post-roll instream ad unit', function () { - const adUnit = utils.deepClone(examples.adUnit_video_instream); - adUnit.params.video.instreamContext = 'post-roll'; - - const adUnits = [adUnit]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.exist.and.to.be.a('number').and.to.equal(-2); - expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); - expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); - }); - - it('should return a server request with one impression when given a valid ad unit without player size', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = undefined; - - const adUnits = [adUnit]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); - expect(serverRequests[0].data.imp[0].video.w).to.equal(null); - expect(serverRequests[0].data.imp[0].video.h).to.equal(null); - expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); - expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); - }); - - it('should return a server request with one impression when given a valid ad unit with an empty player size', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = []; - - const adUnits = [adUnit]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); - expect(serverRequests[0].data.imp[0].video.w).to.equal(null); - expect(serverRequests[0].data.imp[0].video.h).to.equal(null); - expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); - expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); - }); - - it('should return a server request with one impression when given a valid ad unit with multiple player sizes', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.mediaTypes.video.playerSize = [[350, 300], [400, 350]]; - - const adUnits = [adUnit]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); - expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); - expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); - }); - - it('should return a server request with one impression when given a valid ad unit without minimum duration', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.minDuration = undefined; - - const adUnits = [adUnit]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); - expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[0].data.imp[0].video.minduration).to.equal(null); - expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); - }); - - it('should return a server request with one impression when given a valid ad unit without maximum duration', function () { - const adUnit = utils.deepClone(examples.adUnit_video_outstream); - adUnit.params.video.maxDuration = undefined; - - const adUnits = [adUnit]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); - expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); - expect(serverRequests[0].data.imp[0].video.maxduration).to.equal(null); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); - }); - - it('should return a server request with two impressions when given two valid ad units with different impression identifiers', function () { - const adUnit_1 = utils.deepClone(examples.adUnit_video_outstream); - adUnit_1.bidId = 'bid_id_1'; - - const adUnit_2 = utils.deepClone(examples.adUnit_video_outstream); - adUnit_2.bidId = 'bid_id_2'; - - const adUnits = [adUnit_1, adUnit_2]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(2); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); - expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); - expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); - expect(serverRequests[0].data.imp[1]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[1].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0`); - expect(serverRequests[0].data.imp[1].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[1].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[1].video.startdelay).to.equal(null); - expect(serverRequests[0].data.imp[1].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[0].data.imp[1].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[0].data.imp[1].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.minDuration); - expect(serverRequests[0].data.imp[1].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.maxDuration); - expect(serverRequests[0].data.imp[1].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[1].params.video.protocols); - }); - - it('should return a server request with one overridden impression when given two valid ad units with identical identifiers', function () { - const adUnit_1 = utils.deepClone(examples.adUnit_video_outstream); - adUnit_1.params.video.minDuration = 10; - - const adUnit_2 = utils.deepClone(examples.adUnit_video_outstream); - adUnit_2.params.video.minDuration = 15; - - const adUnits = [adUnit_1, adUnit_2]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); - expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.minDuration); - expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.maxDuration); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[1].params.video.protocols); - }); - - it('should return two server requests with one impression when given two valid ad units with different bid request identifiers', function () { - const adUnit_1 = utils.deepClone(examples.adUnit_video_outstream); - adUnit_1.bidderRequestId = 'bidder_request_id_1'; - - const adUnit_2 = utils.deepClone(examples.adUnit_video_outstream); - adUnit_2.bidderRequestId = 'bidder_request_id_2'; - - const adUnits = [adUnit_1, adUnit_2]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(2); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[0].data.imp[0].video.startdelay).to.equal(null); - expect(serverRequests[0].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[0].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.minDuration); - expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].params.video.maxDuration); - expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.protocols); - expect(serverRequests[1].data).to.exist.and.to.be.an('object'); - expect(serverRequests[1].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[1].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[1].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[1].bidId}_0`); - expect(serverRequests[1].data.imp[0].video).to.exist.and.to.be.an('object'); - expect(serverRequests[1].data.imp[0].video.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].params.video.mimes); - expect(serverRequests[1].data.imp[0].video.startdelay).to.equal(null); - expect(serverRequests[1].data.imp[0].video.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][0]); - expect(serverRequests[1].data.imp[0].video.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.playerSize[0][1]); - expect(serverRequests[1].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.minDuration); - expect(serverRequests[1].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].params.video.maxDuration); - expect(serverRequests[1].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[1].params.video.protocols); - }); - }); - - describe('Native', function () { - it('should return a server request with one impression when given a valid ad unit', function () { - const adUnits = [examples.adUnit_native]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnit_native); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}_0`); - expect(serverRequests[0].data.imp[0].native).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.imp[0].native.request).to.exist.and.to.be.a('string').and.to.equal(JSON.stringify(examples.serverRequest_native.data.imp[0].native.request)) - }); - }); - }); - - describe('Site', function () { - it('should return a server request with site information when given a valid ad unit and a valid ad unit context', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = examples.adUnitContext; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.site).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.site.page).to.exist.and.to.be.an('string').and.to.equal(adUnitContext.refererInfo.referer); - expect(serverRequests[0].data.site.id).to.equal(undefined); - expect(serverRequests[0].data.site.domain).to.exist.and.to.be.an('string').and.to.equal('we-are-adot.com'); - expect(serverRequests[0].data.site.name).to.exist.and.to.be.an('string').and.to.equal('we-are-adot.com'); - }); - - it('should return a server request without site information when not given an ad unit context', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = undefined; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.site).to.equal(null); - }); - - it('should return a server request without site information when given an ad unit context without referer information', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.refererInfo = undefined; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.site).to.equal(null); - }); - - it('should return a server request without site information when given an ad unit context with invalid referer information', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.refererInfo = 'bad_referer_information'; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.site).to.equal(null); - }); - - it('should return a server request without site information when given an ad unit context without referer', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.refererInfo.referer = undefined; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.site).to.equal(null); - }); - - it('should return a server request without site information when given an ad unit context with an invalid referer', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.refererInfo.referer = {}; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.site).to.equal(null); - }); - - it('should return a server request without site information when given an ad unit context with a misformatted referer', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.refererInfo.referer = 'we-are-adot'; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.site).to.equal(null); - }); - }); - - describe('Device', function () { - it('should return a server request with device information when given a valid ad unit and a valid ad unit context', function () { - const adUnits = [examples.adUnit_banner]; - - const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.device).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.device.ua).to.exist.and.to.be.a('string'); - expect(serverRequests[0].data.device.language).to.exist.and.to.be.a('string'); - }); - }); - - describe('Regs', function () { - it('should return a server request with regulations information when given a valid ad unit and a valid ad unit context with GDPR applying', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = examples.adUnitContext; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.regs).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.regs.ext).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.regs.ext.gdpr).to.exist.and.to.be.a('boolean').and.to.equal(adUnitContext.gdprConsent.gdprApplies); - }); - - it('should return a server request with regulations information when given a valid ad unit and a valid ad unit context with GDPR not applying', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.gdprConsent.gdprApplies = false; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.regs).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.regs.ext).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.regs.ext.gdpr).to.exist.and.to.be.a('boolean').and.to.equal(adUnitContext.gdprConsent.gdprApplies); - }); - - it('should return a server request without regulations information when not given an ad unit context', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = undefined; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.regs).to.equal(null); - }); - - it('should return a server request without regulations information when given an ad unit context without GDPR information', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.gdprConsent = undefined; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.regs).to.equal(null); - }); - - it('should return a server request without regulations information when given an ad unit context with invalid GDPR information', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.gdprConsent = 'bad_gdpr_consent'; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.regs).to.equal(null); - }); - - it('should return a server request without regulations information when given an ad unit context with invalid GDPR application information', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.gdprConsent.gdprApplies = 'bad_gdpr_applies'; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.regs).to.equal(null); - }); - }); - - describe('User', function () { - it('should return a server request with user information when given a valid ad unit and a valid ad unit context', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = examples.adUnitContext; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.user).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.user.ext).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.user.ext.consent).to.exist.and.to.be.a('string').and.to.equal(adUnitContext.gdprConsent.consentString); - }); - - it('should return a server request without user information when not given an ad unit context', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = undefined; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.user).to.equal(null); - }); - - it('should return a server request without user information when given an ad unit context without GDPR information', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.gdprConsent = undefined; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.user).to.equal(null); - }); - - it('should return a server request without user information when given an ad unit context with invalid GDPR information', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.gdprConsent = 'bad_gdpr_consent'; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.user).to.equal(null); - }); - - it('should return a server request without user information when given an ad unit context with an invalid consent string', function () { - const adUnits = [examples.adUnit_banner]; - - const adUnitContext = utils.deepClone(examples.adUnitContext); - adUnitContext.gdprConsent.consentString = true; - - const serverRequests = spec.buildRequests(adUnits, adUnitContext); - - expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); - expect(serverRequests[0].data).to.exist.and.to.be.an('object'); - expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); - expect(serverRequests[0].data.user).to.equal(null); - }); - }); - }); - - describe('interpretResponse', function () { - describe('General', function () { - it('should return an ad when given a valid server response with one bid with USD currency', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.cur = 'USD'; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(null); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); - expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return two ads when given a valid server response with two bids', function () { - const serverRequest = examples.serverRequest_banner_twoImps; - - const serverResponse = examples.serverResponse_banner_twoBids; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - const adm2WithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[1].adm, serverResponse.body.seatbid[0].bid[1].price); - - expect(ads).to.be.an('array').and.to.have.length(2); - - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(null); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); - expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); - expect(ads[0].renderer).to.equal(null); - expect(ads[1].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[1].bidId); - expect(ads[1].ad).to.exist.and.to.be.a('string').and.to.have.string(adm2WithAuctionPriceReplaced); - expect(ads[1].adUrl).to.equal(null); - expect(ads[1].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[1].crid); - expect(ads[1].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[1].price); - expect(ads[1].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[1].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[1].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[1].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[1].h); - expect(ads[1].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[1].w); - expect(ads[1].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); - expect(ads[1].renderer).to.equal(null); - }); - - it('should return no ad when not given a server response', function () { - const ads = spec.interpretResponse(null); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when not given a server response body', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given an invalid server response body', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body = 'invalid_body'; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response without seat bids', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with invalid seat bids', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid = 'invalid_seat_bids'; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with an empty seat bids array', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid = []; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with an invalid seat bid', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid = 'invalid_bids'; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with an empty bids array', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid = []; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with an invalid bid', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid = ['invalid_bid']; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid without currency', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.cur = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid currency', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.cur = {}; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid without impression identifier', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].impid = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid impression identifier', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].impid = {}; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid without creative identifier', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].crid = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid creative identifier', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].crid = {}; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid without ad markup and ad serving URL', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].adm = undefined; - serverResponse.body.seatbid[0].bid[0].nurl = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid ad markup', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].adm = {}; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an ad markup without auction price macro', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].adm = 'creative_data'; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid ad serving URL', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].nurl = {}; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an ad serving URL without auction price macro', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].nurl = 'win_notice_url'; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid without bid price', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].price = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid bid price', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].price = {}; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid without extension', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].ext = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid extension', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].ext = 'bad_ext'; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid without adot extension', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].ext.adot = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid adot extension', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].ext.adot = 'bad_adot_ext'; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid without media type', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].ext.adot.media_type = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid media type', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].ext.adot.media_type = {}; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an unknown media type', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].ext.adot.media_type = 'unknown_media_type'; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and no server request', function () { - const serverRequest = undefined; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and an invalid server request', function () { - const serverRequest = 'bad_server_request'; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request without bid request', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest.data = undefined; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request with an invalid bid request', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest.data = 'bad_bid_request'; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request without impression', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest.data.imp = undefined; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request with an invalid impression field', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest.data.imp = 'invalid_impressions'; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request without matching impression', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest.data.imp[0].id = 'unknown_imp_id'; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request without internal data', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest._adot_internal = undefined; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request with invalid internal data', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest._adot_internal = 'bad_internal_data'; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request without internal impression data', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest._adot_internal.impressions = undefined; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request with invalid internal impression data', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest._adot_internal.impressions = 'bad_internal_impression_data'; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request without matching internal impression', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest._adot_internal.impressions[0].impressionId = 'unknown_imp_id'; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request without internal impression ad unit code', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest._adot_internal.impressions[0].adUnitCode = undefined; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request with an invalid internal impression ad unit code', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest._adot_internal.impressions[0].adUnitCode = {}; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - }); - - describe('Banner', function () { - it('should return an ad when given a valid server response with one bid', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = examples.serverResponse_banner; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(null); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); - expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return an ad when given a valid server response with one bid without a win notice URL', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].nurl = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.equal(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(null); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); - expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return an ad when given a valid server response with one bid using an ad serving URL', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].adm = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const nurlWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].nurl, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.equal(null); - expect(ads[0].adUrl).to.exist.and.to.be.a('string').and.to.equal(nurlWithAuctionPriceReplaced); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); - expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return no ad when given a server response with a bid without height', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].h = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid height', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].h = {}; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid without width', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].w = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid width', function () { - const serverRequest = examples.serverRequest_banner; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - serverResponse.body.seatbid[0].bid[0].w = {}; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request without banner impression', function () { - const serverRequest = utils.deepClone(examples.serverRequest_banner); - serverRequest.data.imp[0].banner = undefined; - - const serverResponse = utils.deepClone(examples.serverResponse_banner); - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - }); - - describe('Video', function () { - it('should return an ad when given a valid server response with one bid on an instream impression', function () { - const serverRequest = examples.serverRequest_video_instream; - - const serverResponse = examples.serverResponse_video_instream; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); - expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return an ad when given a valid server response with one bid on an outstream impression', function () { - const serverRequest = examples.serverRequest_video_outstream; - - const serverResponse = examples.serverResponse_video_outstream; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); - expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); - expect(ads[0].renderer).to.be.an('object'); - }); - - it('should return two ads when given a valid server response with two bids on both instream and outstream impressions', function () { - const serverRequest = examples.serverRequest_video_instream_outstream; - - const serverResponse = examples.serverResponse_video_instream_outstream; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - const adm2WithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[1].adm, serverResponse.body.seatbid[0].bid[1].price); - - expect(ads).to.be.an('array').and.to.have.length(2); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); - expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); - expect(ads[0].renderer).to.equal(null); - expect(ads[1].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[1].bidId); - expect(ads[1].ad).to.exist.and.to.be.a('string').and.to.have.string(adm2WithAuctionPriceReplaced); - expect(ads[1].adUrl).to.equal(null); - expect(ads[1].vastXml).to.equal(adm2WithAuctionPriceReplaced); - expect(ads[1].vastUrl).to.equal(null); - expect(ads[1].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[1].crid); - expect(ads[1].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[1].price); - expect(ads[1].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[1].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[1].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.equal(serverRequest.data.imp[1].video.h); - expect(ads[0].width).to.equal(serverRequest.data.imp[1].video.w); - expect(ads[1].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); - expect(ads[1].renderer).to.be.an('object'); - }); - - it('should return an ad when given a valid server response with one bid without a win notice URL', function () { - const serverRequest = examples.serverRequest_video_instream; - - const serverResponse = utils.deepClone(examples.serverResponse_video_instream); - serverResponse.body.seatbid[0].bid[0].nurl = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); - expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return an ad when given a valid server response with one bid using an ad serving URL', function () { - const serverRequest = examples.serverRequest_video_instream; - - const serverResponse = utils.deepClone(examples.serverResponse_video_instream); - serverResponse.body.seatbid[0].bid[0].adm = undefined; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const nurlWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].nurl, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.equal(null); - expect(ads[0].adUrl).to.exist.and.to.be.a('string').and.to.have.string(nurlWithAuctionPriceReplaced); - expect(ads[0].vastXml).to.equal(null); - expect(ads[0].vastUrl).to.equal(nurlWithAuctionPriceReplaced); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); - expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return an ad when given a valid server response with a bid with a video height', function () { - const serverRequest = examples.serverRequest_video_instream; - - const serverResponse = utils.deepClone(examples.serverResponse_video_instream); - serverResponse.body.seatbid[0].bid[0].h = 500; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); - expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return an ad when given a valid server response with a bid with a video width', function () { - const serverRequest = examples.serverRequest_video_instream; - - const serverResponse = utils.deepClone(examples.serverResponse_video_instream); - serverResponse.body.seatbid[0].bid[0].w = 500; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.equal(serverRequest.data.imp[0].video.h); - expect(ads[0].width).to.equal(serverRequest.data.imp[0].video.w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return an ad when given a valid server response with a bid with a video width and height', function () { - const serverRequest = examples.serverRequest_video_instream; - - const serverResponse = utils.deepClone(examples.serverResponse_video_instream); - serverResponse.body.seatbid[0].bid[0].w = 500; - serverResponse.body.seatbid[0].bid[0].h = 400; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.equal(serverResponse.body.seatbid[0].bid[0].h); - expect(ads[0].width).to.equal(serverResponse.body.seatbid[0].bid[0].w); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return an ad when given a valid server response and server request with a video impression without width', function () { - const serverRequest = utils.deepClone(examples.serverRequest_video_instream); - serverRequest.data.imp[0].video.w = null; - - const serverResponse = utils.deepClone(examples.serverResponse_video_instream); - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.equal(null); - expect(ads[0].width).to.equal(null); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return an ad when given a valid server response and server request with a video impression without height', function () { - const serverRequest = utils.deepClone(examples.serverRequest_video_instream); - serverRequest.data.imp[0].video.h = null; - - const serverResponse = utils.deepClone(examples.serverResponse_video_instream); - - const ads = spec.interpretResponse(serverResponse, serverRequest); - const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); - expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); - expect(ads[0].vastUrl).to.equal(null); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].height).to.equal(null); - expect(ads[0].width).to.equal(null); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); - expect(ads[0].renderer).to.equal(null); - }); - - it('should return no ad when given a server response with a bid with an invalid height', function () { - const serverRequest = examples.serverRequest_video_instream; - - const serverResponse = utils.deepClone(examples.serverResponse_video_instream); - serverResponse.body.seatbid[0].bid[0].h = {}; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a server response with a bid with an invalid width', function () { - const serverRequest = examples.serverRequest_video_instream; - - const serverResponse = utils.deepClone(examples.serverResponse_video_instream); - serverResponse.body.seatbid[0].bid[0].w = {}; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - it('should return no ad when given a valid server response and a server request without video impression', function () { - const serverRequest = utils.deepClone(examples.serverRequest_video_instream); - serverRequest.data.imp[0].video = undefined; - - const serverResponse = utils.deepClone(examples.serverResponse_video_instream); - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(0); - }); - - describe('Outstream renderer', function () { - function spyAdRenderingQueue(ad) { - const spy = sinon.spy(ad.renderer, 'push'); - - this.sinonSpies.push(spy); - } - - function executeAdRenderer(ad, onRendererExecution, done) { - executeRenderer(ad.renderer, ad); - - setTimeout(() => { - try { - onRendererExecution(); - } catch (err) { - done(err); - } - - done() - }, 100); - } - - before('Bind helper functions to the Mocha context', function () { - this.spyAdRenderingQueue = spyAdRenderingQueue.bind(this); - - window.VASTPlayer = function VASTPlayer() {}; - window.VASTPlayer.prototype.loadXml = function loadXml() { - return new Promise((resolve, reject) => resolve()) - }; - window.VASTPlayer.prototype.load = function load() { - return new Promise((resolve, reject) => resolve()) - }; - window.VASTPlayer.prototype.on = function on(event, callback) {}; - window.VASTPlayer.prototype.startAd = function startAd() {}; - }); - - beforeEach('Initialize the Sinon spies list', function () { - this.sinonSpies = []; - }); - - afterEach('Clear the registered Sinon spies', function () { - this.sinonSpies.forEach(spy => spy.restore()); - }); - - after('clear data', () => { - window.VASTPlayer = null; - }); - - it('should return an ad with valid renderer', function () { - const serverRequest = examples.serverRequest_video_outstream; - const serverResponse = examples.serverResponse_video_outstream; - - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].renderer).to.be.an('object'); - }); - - it('should append a command to the ad rendering queue when executing the renderer', function (done) { - const serverRequest = examples.serverRequest_video_outstream; - const serverResponse = examples.serverResponse_video_outstream; - - const [ad] = spec.interpretResponse(serverResponse, serverRequest); - - this.spyAdRenderingQueue(ad); - - executeAdRenderer(ad, () => { - expect(ad.renderer.push.calledOnce).to.equal(true); - expect(ad.renderer.push.firstCall.args[0]).to.exist.and.to.be.a('function'); - }, done); - }); - }); - }); - - describe('Native', function () { - it('should return an ad when given a valid server response with one bid', function () { - const serverRequest = examples.serverRequest_native; - const serverResponse = examples.serverResponse_native; - const native = JSON.parse(serverResponse.body.seatbid[0].bid[0].adm).native; - const {link, assets} = native; - const ads = spec.interpretResponse(serverResponse, serverRequest); - - expect(ads).to.be.an('array').and.to.have.length(1); - expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); - expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); - expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); - expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('native'); - expect(ads[0].native).to.exist.and.to.be.an('object'); - expect(Object.keys(ads[0].native)).to.have.length(10); - expect(ads[0].native.title).to.equal(assets[0].title.text); - expect(ads[0].native.icon.url).to.equal(assets[1].img.url); - expect(ads[0].native.icon.width).to.equal(assets[1].img.w); - expect(ads[0].native.icon.height).to.equal(assets[1].img.h); - expect(ads[0].native.image.url).to.equal(assets[2].img.url); - expect(ads[0].native.image.width).to.equal(assets[2].img.w); - expect(ads[0].native.image.height).to.equal(assets[2].img.h); - expect(ads[0].native.sponsoredBy).to.equal(assets[3].data.value); - expect(ads[0].native.body).to.equal(assets[4].data.value); - expect(ads[0].native.cta).to.equal(assets[5].data.value); - expect(ads[0].native.clickUrl).to.equal(link.url); - }); - }); + describe('getFloor', function () { + it('should return 0 if getFloor is not a function', function () { + const floor = spec.getFloor({ getFloor: 0 }); + expect(floor).to.deep.equal(0); + }) + + it('should return floor result if currency are correct', function () { + const currency = 'EUR'; + const floorResult = 2; + const fn = sinon.stub().callsFake(() => ({ currency, floor: floorResult })) + const adUnit = { getFloor: fn }; + const size = {}; + const mediaType = {}; + + const floor = spec.getFloor(adUnit, size, mediaType, currency); + expect(floor).to.deep.equal(floorResult); + expect(fn.calledOnce).to.equal(true); + expect(fn.calledWithExactly({ currency, mediaType, size })).to.equal(true); + }) + + it('should return floor result if currency are not correct', function () { + const currency = 'EUR'; + const floorResult = 2; + const fn = sinon.stub().callsFake(() => ({ currency: 'wrong_currency', floor: floorResult })) + const adUnit = { getFloor: fn }; + const size = {}; + const mediaType = {}; + + const floor = spec.getFloor(adUnit, size, mediaType, currency); + expect(floor).to.deep.equal(0); + expect(fn.calledOnce).to.equal(true); + expect(fn.calledWithExactly({ currency, mediaType, size })).to.equal(true); + }) }); }); diff --git a/test/spec/modules/adpartnerBidAdapter_spec.js b/test/spec/modules/adpartnerBidAdapter_spec.js index d30ef7ebf71..94b56f7735b 100644 --- a/test/spec/modules/adpartnerBidAdapter_spec.js +++ b/test/spec/modules/adpartnerBidAdapter_spec.js @@ -52,7 +52,7 @@ describe('AdpartnerAdapter', function () { }); describe('buildRequests', function () { - let validEndpoint = ENDPOINT_PROTOCOL + '://' + ENDPOINT_DOMAIN + ENDPOINT_PATH + '?tag=123,456&sizes=300x250|300x600,728x90&referer=https%3A%2F%2Ftest.domain'; + let validEndpoint = ENDPOINT_PROTOCOL + '://' + ENDPOINT_DOMAIN + ENDPOINT_PATH + '?tag=123,456&partner=777&sizes=300x250|300x600,728x90,300x250&referer=https%3A%2F%2Ftest.domain'; let validRequest = [ { @@ -72,6 +72,15 @@ describe('AdpartnerAdapter', function () { 'adUnitCode': 'adunit-code-2', 'sizes': [[728, 90]], 'bidId': '22aidtbx5eabd9' + }, + { + 'bidder': BIDDER_CODE, + 'params': { + 'partnerId': 777 + }, + 'adUnitCode': 'partner-code-3', + 'sizes': [[300, 250]], + 'bidId': '5d4531d5a6c013' } ]; @@ -100,6 +109,9 @@ describe('AdpartnerAdapter', function () { expect(payload[1].unitId).to.equal(456); expect(payload[1].sizes).to.deep.equal([[728, 90]]); expect(payload[1].bidId).to.equal('22aidtbx5eabd9'); + expect(payload[2].partnerId).to.equal(777); + expect(payload[2].sizes).to.deep.equal([[300, 250]]); + expect(payload[2].bidId).to.equal('5d4531d5a6c013'); }); }); @@ -113,40 +125,45 @@ describe('AdpartnerAdapter', function () { describe('interpretResponse', function () { const bidRequest = { 'method': 'POST', - 'url': ENDPOINT_PROTOCOL + '://' + ENDPOINT_DOMAIN + ENDPOINT_PATH + '?tag=123,456&code=adunit-code-1,adunit-code-2&bid=30b31c1838de1e,22aidtbx5eabd9&sizes=300x250|300x600,728x90&referer=https%3A%2F%2Ftest.domain', - 'data': '[{"unitId": 13144370,"adUnitCode": "div-gpt-ad-1460505748561-0","sizes": [[300, 250], [300, 600]],"bidId": "2bdcb0b203c17d","referer": "https://test.domain/index.html"},{"unitId": 13144370,"adUnitCode":"div-gpt-ad-1460505748561-1","sizes": [[768, 90]],"bidId": "3dc6b8084f91a8","referer": "https://test.domain/index.html"}]' + 'url': ENDPOINT_PROTOCOL + '://' + ENDPOINT_DOMAIN + ENDPOINT_PATH + '?tag=123,456&partner=777code=adunit-code-1,adunit-code-2,partner-code-3&bid=30b31c1838de1e,22aidtbx5eabd9,5d4531d5a6c013&sizes=300x250|300x600,728x90,300x250&referer=https%3A%2F%2Ftest.domain', + 'data': '[{"unitId": 13144370,"adUnitCode": "div-gpt-ad-1460505748561-0","sizes": [[300, 250], [300, 600]],"bidId": "2bdcb0b203c17d","referer": "https://test.domain/index.html"},' + + '{"unitId": 13144370,"adUnitCode":"div-gpt-ad-1460505748561-1","sizes": [[768, 90]],"bidId": "3dc6b8084f91a8","referer": "https://test.domain/index.html"},' + + '{"unitId": 0,"partnerId": 777,"adUnitCode":"div-gpt-ad-1460505748561-2","sizes": [[300, 250]],"bidId": "5d4531d5a6c013","referer": "https://test.domain/index.html"}]' }; const bidResponse = { body: { 'div-gpt-ad-1460505748561-0': - { - 'ad': '
ad
', - 'width': 300, - 'height': 250, - 'creativeId': '8:123456', - 'syncs': [ - {'type': 'image', 'url': 'https://test.domain/tracker_1.gif'}, - {'type': 'image', 'url': 'https://test.domain/tracker_2.gif'}, - {'type': 'image', 'url': 'https://test.domain/tracker_3.gif'} - ], - 'winNotification': [ - { - 'method': 'POST', - 'path': '/hb/bid_won?test=1', - 'data': { - 'ad': [ - {'dsp': 8, 'id': 800008, 'cost': 1.0e-5, 'nurl': 'https://test.domain/'} - ], - 'unit_id': 1234, - 'site_id': 123 + { + 'ad': '
ad
', + 'width': 300, + 'height': 250, + 'creativeId': '8:123456', + 'adomain': [ + 'test.domain' + ], + 'syncs': [ + {'type': 'image', 'url': 'https://test.domain/tracker_1.gif'}, + {'type': 'image', 'url': 'https://test.domain/tracker_2.gif'}, + {'type': 'image', 'url': 'https://test.domain/tracker_3.gif'} + ], + 'winNotification': [ + { + 'method': 'POST', + 'path': '/hb/bid_won?test=1', + 'data': { + 'ad': [ + {'dsp': 8, 'id': 800008, 'cost': 1.0e-5, 'nurl': 'https://test.domain/'} + ], + 'unit_id': 1234, + 'site_id': 123 + } } - } - ], - 'cpm': 0.01, - 'currency': 'USD', - 'netRevenue': true - } + ], + 'cpm': 0.01, + 'currency': 'USD', + 'netRevenue': true + } }, headers: {} }; @@ -160,6 +177,7 @@ describe('AdpartnerAdapter', function () { expect(result[0].creativeId).to.equal('8:123456'); expect(result[0].currency).to.equal('USD'); expect(result[0].ttl).to.equal(60); + expect(result[0].meta.advertiserDomains).to.deep.equal(['test.domain']); expect(result[0].winNotification[0]).to.deep.equal({'method': 'POST', 'path': '/hb/bid_won?test=1', 'data': {'ad': [{'dsp': 8, 'id': 800008, 'cost': 1.0e-5, 'nurl': 'https://test.domain/'}], 'unit_id': 1234, 'site_id': 123}}); }); }); @@ -181,7 +199,10 @@ describe('AdpartnerAdapter', function () { 'winNotification': [], 'cpm': 0.01, 'currency': 'USD', - 'netRevenue': true + 'netRevenue': true, + 'adomain': [ + 'test.domain' + ], }; it('fill ad for response', function () { @@ -193,6 +214,7 @@ describe('AdpartnerAdapter', function () { expect(result.creativeId).to.equal('8:123456'); expect(result.currency).to.equal('USD'); expect(result.ttl).to.equal(60); + expect(result.meta.advertiserDomains).to.deep.equal(['test.domain']); }); }); @@ -231,4 +253,84 @@ describe('AdpartnerAdapter', function () { expect(ajaxStub.firstCall.args[1]).to.deep.equal(JSON.stringify(bid.winNotification[0].data)); }); }); + + describe('getUserSyncs', function () { + const bidResponse = [{ + body: { + 'div-gpt-ad-1460505748561-0': + { + 'ad': '
ad
', + 'width': 300, + 'height': 250, + 'creativeId': '8:123456', + 'adomain': [ + 'test.domain' + ], + 'syncs': [ + {'type': 'image', 'link': 'https://test.domain/tracker_1.gif'}, + {'type': 'image', 'link': 'https://test.domain/tracker_2.gif'}, + {'type': 'image', 'link': 'https://test.domain/tracker_3.gif'} + ], + 'winNotification': [ + { + 'method': 'POST', + 'path': '/hb/bid_won?test=1', + 'data': { + 'ad': [ + {'dsp': 8, 'id': 800008, 'cost': 1.0e-5, 'nurl': 'https://test.domain/'} + ], + 'unit_id': 1234, + 'site_id': 123 + } + } + ], + 'cpm': 0.01, + 'currency': 'USD', + 'netRevenue': true + } + }, + headers: {} + }]; + + it('should return nothing when sync is disabled', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': false + }; + + let syncs = spec.getUserSyncs(syncOptions); + expect(syncs).to.deep.equal([]); + }); + + it('should register image sync when only image is enabled where gdprConsent is undefined', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true + }; + + const gdprConsent = undefined; + let syncs = spec.getUserSyncs(syncOptions, bidResponse, gdprConsent); + expect(syncs.length).to.equal(3); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://test.domain/tracker_1.gif'); + }); + + it('should register image sync when only image is enabled where gdprConsent is defined', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true + }; + const gdprConsent = { + consentString: 'someString', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + + let syncs = spec.getUserSyncs(syncOptions, bidResponse, gdprConsent); + expect(syncs.length).to.equal(3); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://test.domain/tracker_1.gif?gdpr=1&gdpr_consent=someString'); + }); + }); }); diff --git a/test/spec/modules/adplusBidAdapter_spec.js b/test/spec/modules/adplusBidAdapter_spec.js new file mode 100644 index 00000000000..840d86c80f1 --- /dev/null +++ b/test/spec/modules/adplusBidAdapter_spec.js @@ -0,0 +1,213 @@ +import {expect} from 'chai'; +import {spec, BIDDER_CODE, ADPLUS_ENDPOINT, } from 'modules/adplusBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +describe('AplusBidAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.be.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + let validRequest = { + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + inventoryId: '30', + adUnitId: '1', + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let validRequest = { + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + inventoryId: '30', + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + + it('should return false when required param types are wrong', function () { + let validRequest = { + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + inventoryId: 30, + adUnitId: '1', + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + + it('should return false when size is not exists', function () { + let validRequest = { + params: { + inventoryId: 30, + adUnitId: '1', + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + + it('should return false when size is wrong', function () { + let validRequest = { + mediaTypes: { + banner: { + sizes: [[300]] + } + }, + params: { + inventoryId: 30, + adUnitId: '1', + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let validRequest = [ + { + bidder: BIDDER_CODE, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + inventoryId: '-1', + adUnitId: '-3', + }, + bidId: '2bdcb0b203c17d' + }, + ]; + + let bidderRequest = { + refererInfo: { + referer: 'https://test.domain' + } + }; + + it('bidRequest HTTP method', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request[0].method).to.equal('GET'); + }); + + it('bidRequest url', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request[0].url).to.equal(ADPLUS_ENDPOINT); + }); + + it('tests bidRequest data is clean and has the right values', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + + expect(request[0].data.bidId).to.equal('2bdcb0b203c17d'); + expect(request[0].data.inventoryId).to.equal('-1'); + expect(request[0].data.adUnitId).to.equal('-3'); + expect(request[0].data.adUnitWidth).to.equal(300); + expect(request[0].data.adUnitHeight).to.equal(250); + expect(request[0].data.sdkVersion).to.equal('1'); + expect(typeof request[0].data.session).to.equal('string'); + expect(request[0].data.session).length(36); + expect(request[0].data.interstitial).to.equal(0); + expect(request[0].data).to.not.have.deep.property('extraData'); + expect(request[0].data).to.not.have.deep.property('yearOfBirth'); + expect(request[0].data).to.not.have.deep.property('gender'); + expect(request[0].data).to.not.have.deep.property('categories'); + expect(request[0].data).to.not.have.deep.property('latitude'); + expect(request[0].data).to.not.have.deep.property('longitude'); + }); + }); + + describe('interpretResponse', function () { + const requestData = { + language: window.navigator.language, + screenWidth: 1440, + screenHeight: 900, + sdkVersion: '1', + inventoryId: '-1', + adUnitId: '-3', + adUnitWidth: 300, + adUnitHeight: 250, + domain: 'tassandigi.com', + pageUrl: 'https%3A%2F%2Ftassandigi.com%2Fserafettin%2Fads.html', + interstitial: 0, + session: '1c02db03-5289-932a-93af-7b4022611fec', + token: '1c02db03-5289-937a-93df-7b4022611fec', + secure: 1, + bidId: '2bdcb0b203c17d', + }; + const bidRequest = { + 'method': 'GET', + 'url': ADPLUS_ENDPOINT, + 'data': requestData, + }; + + const bidResponse = { + body: [ + { + 'ad': '
ad
', + 'advertiserDomains': [ + 'advertiser.com' + ], + 'categoryIDs': [ + 'IAB-111' + ], + 'cpm': 3.57, + 'creativeID': '1', + 'currency': 'TRY', + 'dealID': '1', + 'height': 300, + 'mediaType': 'banner', + 'netRevenue': true, + 'requestID': '2bdcb0b203c17d', + 'ttl': 300, + 'width': 250 + } + ], + headers: {} + }; + + const emptyBidResponse = { + body: null, + }; + + it('returns an empty array when the result body is not valid', function () { + const result = spec.interpretResponse(emptyBidResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('result is correct', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0].requestId).to.equal('2bdcb0b203c17d'); + expect(result[0].cpm).to.equal(3.57); + expect(result[0].width).to.equal(250); + expect(result[0].height).to.equal(300); + expect(result[0].creativeId).to.equal('1'); + expect(result[0].currency).to.equal('TRY'); + expect(result[0].dealId).to.equal('1'); + expect(result[0].mediaType).to.equal('banner'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + expect(result[0].meta.advertiserDomains).to.deep.equal(['advertiser.com']); + expect(result[0].meta.secondaryCatIds).to.deep.equal(['IAB-111']); + }); + }); +}); diff --git a/test/spec/modules/adpod_spec.js b/test/spec/modules/adpod_spec.js index 5e4bcce1fe6..a6164f919ef 100644 --- a/test/spec/modules/adpod_spec.js +++ b/test/spec/modules/adpod_spec.js @@ -73,16 +73,11 @@ describe('adpod.js', function () { mediaType: 'video' }; - let bidderRequest = { - adUnitCode: 'adUnit_123', - mediaTypes: { - video: { - context: 'outstream' - } - } - } + let videoMT = { + context: 'outstream' + }; - callPrebidCacheHook(callbackFn, auctionInstance, bid, function () {}, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bid, function () {}, videoMT); expect(callbackResult).to.equal(true); }); @@ -132,22 +127,16 @@ describe('adpod.js', function () { } }; - let bidderRequest = { - adUnitCode: 'adpod_1', - auctionId: 'no_defer_123', - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 300, - durationRangeSec: [15, 30, 45], - requireExactDuration: false - } - }, + let videoMT = { + context: ADPOD, + playerSize: [[300, 300]], + adPodDurationSec: 300, + durationRangeSec: [15, 30, 45], + requireExactDuration: false }; - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); // check if bid adsereverTargeting is setup expect(callbackResult).to.be.null; @@ -214,22 +203,16 @@ describe('adpod.js', function () { durationBucket: 30 } }; - let bidderRequest = { - adUnitCode: 'adpod_1', - auctionId: 'full_abc123', - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 120, - durationRangeSec: [15, 30], - requireExactDuration: false - } - } + let videoMT = { + context: ADPOD, + playerSize: [[300, 300]], + adPodDurationSec: 120, + durationRangeSec: [15, 30], + requireExactDuration: false }; - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledTwice).to.equal(true); @@ -276,21 +259,15 @@ describe('adpod.js', function () { durationBucket: 30 } }; - let bidderRequest = { - adUnitCode: 'adpod_2', - auctionId: 'timer_abc234', - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 120, - durationRangeSec: [15, 30], - requireExactDuration: true - } - } + let videoMT = { + context: ADPOD, + playerSize: [[300, 300]], + adPodDurationSec: 120, + durationRangeSec: [15, 30], + requireExactDuration: true }; - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse, afterBidAddedSpy, videoMT); clock.tick(31); expect(callbackResult).to.be.null; @@ -370,23 +347,17 @@ describe('adpod.js', function () { } }; - let bidderRequest = { - adUnitCode: 'adpod_3', - auctionId: 'multi_call_abc345', - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 45, - durationRangeSec: [15, 30], - requireExactDuration: false - } - } + let videoMT = { + context: ADPOD, + playerSize: [[300, 300]], + adPodDurationSec: 45, + durationRangeSec: [15, 30], + requireExactDuration: false }; - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse3, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse3, afterBidAddedSpy, videoMT); clock.next(); expect(callbackResult).to.be.null; @@ -459,22 +430,16 @@ describe('adpod.js', function () { } }; - let bidderRequest = { - adUnitCode: 'adpod_4', - auctionId: 'no_category_abc345', - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 45, - durationRangeSec: [15, 30], - requireExactDuration: false - } - } + let videoMT = { + context: ADPOD, + playerSize: [[300, 300]], + adPodDurationSec: 45, + durationRangeSec: [15, 30], + requireExactDuration: false }; - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledTwice).to.equal(true); @@ -519,21 +484,15 @@ describe('adpod.js', function () { } }; - let bidderRequest = { - adUnitCode: 'adpod_5', - auctionId: 'missing_category_abc345', - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 45, - durationRangeSec: [15, 30], - requireExactDuration: false - } - } + let videoMT = { + context: ADPOD, + playerSize: [[300, 300]], + adPodDurationSec: 45, + durationRangeSec: [15, 30], + requireExactDuration: false }; - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledOnce).to.equal(true); @@ -596,22 +555,16 @@ describe('adpod.js', function () { durationBucket: 45 } }; - let bidderRequest = { - adUnitCode: 'adpod_4', - auctionId: 'duplicate_def123', - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 120, - durationRangeSec: [15, 30, 45], - requireExactDuration: false - } - } + let videoMT = { + context: ADPOD, + playerSize: [[300, 300]], + adPodDurationSec: 120, + durationRangeSec: [15, 30, 45], + requireExactDuration: false }; - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledTwice).to.equal(true); @@ -669,22 +622,16 @@ describe('adpod.js', function () { durationBucket: 30 } }; - let bidderRequest = { - adUnitCode: 'adpod_5', - auctionId: 'error_xyz123', - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 120, - durationRangeSec: [15, 30, 45], - requireExactDuration: false - } - } + let videoMT = { + context: ADPOD, + playerSize: [[300, 300]], + adPodDurationSec: 120, + durationRangeSec: [15, 30, 45], + requireExactDuration: false }; - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); expect(doCallbacksIfTimedoutStub.calledTwice).to.equal(true); expect(logWarnStub.calledOnce).to.equal(true); @@ -742,21 +689,15 @@ describe('adpod.js', function () { } }; - let bidderRequest = { - adUnitCode: 'adpod_5', - auctionId: 'test_category_abc345', - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 45, - durationRangeSec: [15, 30], - requireExactDuration: false - } - } + let videoMT = { + context: ADPOD, + playerSize: [[300, 300]], + adPodDurationSec: 45, + durationRangeSec: [15, 30], + requireExactDuration: false }; - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledOnce).to.equal(true); @@ -820,22 +761,16 @@ describe('adpod.js', function () { } }; - let bidderRequest = { - adUnitCode: 'adpod_1', - auctionId: 'no_defer_123', - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 300, - durationRangeSec: [15, 30, 45], - requireExactDuration: false - } - }, + let videoMT = { + context: ADPOD, + playerSize: [[300, 300]], + adPodDurationSec: 300, + durationRangeSec: [15, 30, 45], + requireExactDuration: false }; - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('tier7_test_15s'); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('12.00_value_15s'); @@ -985,7 +920,7 @@ describe('adpod.js', function () { }, vastXml: 'test XML here' }; - const bidderRequestNoExact = { + const adUnitNoExact = { mediaTypes: { video: { context: ADPOD, @@ -996,7 +931,7 @@ describe('adpod.js', function () { } } }; - const bidderRequestWithExact = { + const adUnitWithExact = { mediaTypes: { video: { context: ADPOD, @@ -1051,7 +986,7 @@ describe('adpod.js', function () { let goodBid = utils.deepClone(adpodTestBid); goodBid.meta.primaryCatId = undefined; - checkVideoBidSetupHook(callbackFn, goodBid, bidderRequestNoExact, {}, ADPOD); + checkVideoBidSetupHook(callbackFn, goodBid, adUnitNoExact, adUnitNoExact.mediaTypes.video, ADPOD); expect(callbackResult).to.be.null; expect(bailResult).to.equal(true); expect(logErrorStub.called).to.equal(false); @@ -1059,7 +994,7 @@ describe('adpod.js', function () { it('returns true when adpod bid is missing iab category while brandCategoryExclusion in config is false', function() { let goodBid = utils.deepClone(adpodTestBid); - checkVideoBidSetupHook(callbackFn, goodBid, bidderRequestNoExact, {}, ADPOD); + checkVideoBidSetupHook(callbackFn, goodBid, adUnitNoExact, adUnitNoExact.mediaTypes.video, ADPOD); expect(callbackResult).to.be.null; expect(bailResult).to.equal(true); expect(logErrorStub.called).to.equal(false); @@ -1067,7 +1002,7 @@ describe('adpod.js', function () { it('returns false when a required property from an adpod bid is missing', function() { function testInvalidAdpodBid(badTestBid, shouldErrorBeLogged) { - checkVideoBidSetupHook(callbackFn, badTestBid, bidderRequestNoExact, {}, ADPOD); + checkVideoBidSetupHook(callbackFn, badTestBid, adUnitNoExact, adUnitNoExact.mediaTypes.video, ADPOD); expect(callbackResult).to.be.null; expect(bailResult).to.equal(false); expect(logErrorStub.called).to.equal(shouldErrorBeLogged); @@ -1108,7 +1043,7 @@ describe('adpod.js', function () { it('when requireExactDuration is true', function() { let goodBid = utils.deepClone(basicBid); - checkVideoBidSetupHook(callbackFn, goodBid, bidderRequestWithExact, {}, ADPOD); + checkVideoBidSetupHook(callbackFn, goodBid, adUnitWithExact, adUnitWithExact.mediaTypes.video, ADPOD); expect(callbackResult).to.be.null; expect(goodBid.video.durationBucket).to.equal(30); @@ -1117,7 +1052,7 @@ describe('adpod.js', function () { let badBid = utils.deepClone(basicBid); badBid.video.durationSeconds = 14; - checkVideoBidSetupHook(callbackFn, badBid, bidderRequestWithExact, {}, ADPOD); + checkVideoBidSetupHook(callbackFn, badBid, adUnitWithExact, adUnitWithExact.mediaTypes.video, ADPOD); expect(callbackResult).to.be.null; expect(badBid.video.durationBucket).to.be.undefined; @@ -1127,7 +1062,7 @@ describe('adpod.js', function () { it('when requireExactDuration is false and bids are bucketed properly', function() { function testRoundingForGoodBId(bid, bucketValue) { - checkVideoBidSetupHook(callbackFn, bid, bidderRequestNoExact, {}, ADPOD); + checkVideoBidSetupHook(callbackFn, bid, adUnitNoExact, adUnitNoExact.mediaTypes.video, ADPOD); expect(callbackResult).to.be.null; expect(bid.video.durationBucket).to.equal(bucketValue); expect(bailResult).to.equal(true); @@ -1157,7 +1092,7 @@ describe('adpod.js', function () { it('when requireExactDuration is false and bid duration exceeds listed buckets', function() { function testRoundingForBadBid(bid) { - checkVideoBidSetupHook(callbackFn, bid, bidderRequestNoExact, {}, ADPOD); + checkVideoBidSetupHook(callbackFn, bid, adUnitNoExact, adUnitNoExact.mediaTypes.video, ADPOD); expect(callbackResult).to.be.null; expect(bid.video.durationBucket).to.be.undefined; expect(bailResult).to.equal(false); diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index 8627941dc80..53f41a6be4e 100644 --- a/test/spec/modules/adprimeBidAdapter_spec.js +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -6,9 +6,13 @@ describe('AdprimebBidAdapter', function () { const bid = { bidId: '23fhj33i987f', bidder: 'adprime', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, params: { - placementId: 0, - traffic: BANNER + placementId: 'testBanner' } }; @@ -40,7 +44,7 @@ describe('AdprimebBidAdapter', function () { expect(serverRequest.method).to.equal('POST'); }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://delta.adprime.com/?c=o&m=multi'); + expect(serverRequest.url).to.equal('https://delta.adprime.com/pbjs'); }); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; @@ -55,17 +59,16 @@ describe('AdprimebBidAdapter', function () { expect(data.gdpr).to.not.exist; expect(data.ccpa).to.not.exist; let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'identeties', 'traffic', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'keywords'); - expect(placement.placementId).to.equal(0); + expect(placement).to.have.keys('placementId', 'bidId', 'identeties', 'adFormat', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'keywords', 'audiences', 'bidFloor'); + expect(placement.placementId).to.equal('testBanner'); expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal(BANNER); + expect(placement.adFormat).to.equal(BANNER); expect(placement.schain).to.be.an('object'); }); it('Returns valid data for mediatype video', function () { const playerSize = [300, 300]; bid.mediaTypes = {}; - bid.params.traffic = VIDEO; bid.mediaTypes[VIDEO] = { playerSize }; @@ -74,7 +77,7 @@ describe('AdprimebBidAdapter', function () { expect(data).to.be.an('object'); let placement = data['placements'][0]; expect(placement).to.be.an('object'); - expect(placement.traffic).to.equal(VIDEO); + expect(placement.adFormat).to.equal(VIDEO); expect(placement.wPlayer).to.equal(playerSize[0]); expect(placement.hPlayer).to.equal(playerSize[1]); }); @@ -137,14 +140,15 @@ describe('AdprimebBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: {} }] }; let bannerResponses = spec.interpretResponse(banner); expect(bannerResponses).to.be.an('array').that.is.not.empty; let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.4); expect(dataItem.width).to.equal(300); @@ -154,6 +158,7 @@ describe('AdprimebBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { const video = { @@ -166,7 +171,8 @@ describe('AdprimebBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: {} }] }; let videoResponses = spec.interpretResponse(video); @@ -174,7 +180,7 @@ describe('AdprimebBidAdapter', function () { let dataItem = videoResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.5); expect(dataItem.vastUrl).to.equal('test.com'); @@ -182,6 +188,7 @@ describe('AdprimebBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret native response', function () { const native = { @@ -199,13 +206,14 @@ describe('AdprimebBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: {} }] }; let nativeResponses = spec.interpretResponse(native); expect(nativeResponses).to.be.an('array').that.is.not.empty; let dataItem = nativeResponses[0]; - expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.4); @@ -218,6 +226,7 @@ describe('AdprimebBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should return an empty array if invalid banner response is passed', function () { const invBanner = { @@ -291,7 +300,7 @@ describe('AdprimebBidAdapter', function () { expect(userSync[0].type).to.exist; expect(userSync[0].url).to.exist; expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://delta.adprime.com/?c=rtb&m=sync'); + expect(userSync[0].url).to.be.equal('https://delta.adprime.com'); }); }); }); diff --git a/test/spec/modules/adqueryBidAdapter_spec.js b/test/spec/modules/adqueryBidAdapter_spec.js new file mode 100644 index 00000000000..4285377e8a7 --- /dev/null +++ b/test/spec/modules/adqueryBidAdapter_spec.js @@ -0,0 +1,185 @@ +import { expect } from 'chai' +import { spec } from 'modules/adqueryBidAdapter.js' +import { newBidder } from 'src/adapters/bidderFactory.js' +import * as utils from '../../../src/utils'; + +describe('adqueryBidAdapter', function () { + const adapter = newBidder(spec) + let bidRequest = { + bidder: 'adquery', + params: { + placementId: '6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897', + type: 'banner300x250' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + } + } + + let expectedResponse = { + 'body': { + 'data': + { + 'requestId': 1, + 'emission_id': 1, + 'eventTracker': 'https://example.com', + 'externalEmissionCodes': 'https://example.com', + 'impressionTracker': 'https://example.com', + 'viewabilityTracker': 'https://example.com', + 'clickTracker': 'https://example.com', + 'link': 'https://example.com', + 'logo': 'https://example.com', + 'medias': [ + { + 'src': 'banner/2021-04-09/938', + 'ext': 'zip', + 'type': 3, + } + ], + 'domain': 'https://example.com', + 'urlAdq': 'https://example.com', + 'creationId': 1, + 'currency': 'PLN', + 'adDomains': ['https://example.com'], + 'tag': ' ', + 'adqLib': 'https://example.com/js/example.js', + 'mediaType': {'width': 300, 'height': 250, 'name': 'banner', 'type': 'banner300x250'}, + 'cpm': 2.5, + 'meta': { + 'advertiserDomains': ['example.com'], + 'mediaType': 'banner', + } + } + } + } + describe('codes', function () { + it('should return a bidder code of adquery', function () { + expect(spec.code).to.equal('adquery') + }) + }) + + describe('isBidRequestValid', function () { + let inValidBid = Object.assign({}, bidRequest) + delete inValidBid.params + it('should return true if all params present', function () { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true) + }) + + it('should return false if any parameter missing', function () { + expect(spec.isBidRequestValid(inValidBid)).to.be.false + }) + }) + + describe('buildRequests', function () { + let req = spec.buildRequests([ bidRequest ], { refererInfo: { } })[0] + let rdata + + it('should return request object', function () { + expect(req).to.not.be.null + }) + + it('should build request data', function () { + expect(req.data).to.not.be.null + }) + + it('should include one request', function () { + rdata = req.data; + expect(rdata.data).to.not.be.null + }) + + it('should include placementCode', function () { + expect(rdata.placementCode).not.be.null + }) + + it('should include qid', function () { + expect(rdata.qid).not.be.null + }) + + it('should include type', function () { + expect(rdata.type !== null).not.be.null + }) + + it('should include all publisher params', function () { + expect(rdata.type !== null && rdata.placementCode !== null).to.be.true + }) + + it('should include bidder', function () { + expect(rdata.bidder !== null).to.be.true + }) + }) + + describe('interpretResponse', function () { + it('should get the correct bid response', function () { + let result = spec.interpretResponse(expectedResponse) + expect(result).to.be.an('array') + }) + + it('validate response params', function() { + const newResponse = spec.interpretResponse(expectedResponse, bidRequest); + expect(newResponse[0].requestId).to.be.equal(1) + }); + it('handles empty bid response', function () { + let response = { + body: {} + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }) + }) + + describe('getUserSyncs', function () { + it('should return iframe sync', function () { + let sync = spec.getUserSyncs() + expect(sync.length).to.equal(1) + expect(sync[0].type === 'iframe') + expect(typeof sync[0].url === 'string') + }) + + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + }); + }) + + describe('test onBidWon function', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('exists and is a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + it('should return nothing', function () { + var response = spec.onBidWon({}); + expect(response).to.be.an('undefined') + expect(utils.triggerPixel.called).to.equal(true); + }); + }) + + describe('onTimeout', function () { + const timeoutData = [{ + timeout: null + }]; + + it('should exists and be a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + it('should include timeoutData', function () { + expect(spec.onTimeout(timeoutData)).to.be.undefined; + }) + }); + + it(`onSetTargeting is present and type function`, function () { + expect(spec.onSetTargeting).to.exist.and.to.be.a('function') + }); +}) diff --git a/test/spec/modules/adqueryIdSystem_spec.js b/test/spec/modules/adqueryIdSystem_spec.js new file mode 100644 index 00000000000..ab98b253b33 --- /dev/null +++ b/test/spec/modules/adqueryIdSystem_spec.js @@ -0,0 +1,74 @@ +import { adqueryIdSubmodule, storage } from 'modules/adqueryIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; +import {amxIdSubmodule} from '../../../modules/amxIdSystem'; +import * as utils from '../../../src/utils'; + +const config = { + storage: { + type: 'html5', + }, +}; + +describe('AdqueryIdSystem', function () { + describe('qid submodule', () => { + it('should expose a "name" property containing qid', () => { + expect(adqueryIdSubmodule.name).to.equal('qid'); + }); + + it('should expose a "gvlid" property containing the GVL ID 902', () => { + expect(adqueryIdSubmodule.gvlid).to.equal(902); + }); + }); + + describe('getId', function() { + let getDataFromLocalStorageStub; + + beforeEach(function() { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + it('gets a adqueryId', function() { + const config = { + params: {} + }; + const callbackSpy = sinon.spy(); + const callback = adqueryIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.eq(`https://bidder.adquery.io/prebid/qid`); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ qid: 'qid' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({qid: 'qid'}); + }); + + it('gets a cached adqueryId', function() { + const config = { + params: {} + }; + getDataFromLocalStorageStub.withArgs('qid').returns('qid'); + + const callbackSpy = sinon.spy(); + const callback = adqueryIdSubmodule.getId(config).callback; + callback(callbackSpy); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({qid: 'qid'}); + }); + + it('allows configurable id url', function() { + const config = { + params: { + url: 'https://bidder.adquery.io' + } + }; + const callbackSpy = sinon.spy(); + const callback = adqueryIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.eq('https://bidder.adquery.io'); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ qid: 'testqid' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({qid: 'testqid'}); + }); + }); +}); diff --git a/test/spec/modules/adrelevantisBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js index 596f3bade5d..b87f9d6b86c 100644 --- a/test/spec/modules/adrelevantisBidAdapter_spec.js +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -228,11 +228,7 @@ describe('AdrelevantisAdapter', function () { .returns({ site: { keywords: 'US Open', - ext: { - data: { - category: 'sports/tennis' - } - } + category: 'sports/tennis' } }); diff --git a/test/spec/modules/adriverBidAdapter_spec.js b/test/spec/modules/adriverBidAdapter_spec.js index c16bc5df5cb..9d410090885 100644 --- a/test/spec/modules/adriverBidAdapter_spec.js +++ b/test/spec/modules/adriverBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/adriverBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as bidderFactory from 'src/adapters/bidderFactory.js'; import { auctionManager } from 'src/auctionManager.js'; + const ENDPOINT = 'https://pb.adriver.ru/cgi-bin/bid.cgi'; describe('adriverAdapter', function () { @@ -15,17 +16,44 @@ describe('adriverAdapter', function () { }); describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'adriver', - 'params': { - 'placementId': '55:test_placement', - 'siteid': 'testSiteID' + const bid = { + bidder: 'adriver', + params: { + placementId: '55:test_placement', + siteid: 'testSiteID' }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600], [300, 250]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600], [300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userIdAsEids: [ + { + source: 'id5-sync.com', + uids: [ + { + id: '', + atype: 1, + ext: { + linkType: 0, + abTestingControlGroup: true + } + } + ] + }, + { + source: 'sharedid.org', + uids: [ + { + id: '01F4W41TMN7NBXBA0PXJMPB7GF', + atype: 1, + ext: { + third: '01F4W41TMN7NBXBA0PXJMPB7GF' + } + } + ] + } + ] }; it('should return true when required params found', function () { @@ -37,27 +65,163 @@ describe('adriverAdapter', function () { let getAdUnitsStub; const floor = 3; - let bidRequests = [ + const bidRequests = [ { - 'bidder': 'adriver', - 'params': { - 'placementId': '55:test_placement', - 'siteid': 'testSiteID', - 'dealid': 'dealidTest' + bidder: 'adriver', + params: { + placementId: '55:test_placement', + siteid: 'testSiteID', + dealid: 'dealidTest' }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600], [300, 250]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600], [300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843', + userIdAsEids: [ + { + source: 'id5-sync.com', + uids: [ + { + id: '', + atype: 1, + ext: { + linkType: 0, + abTestingControlGroup: true + } + } + ] + }, + { + source: 'sharedid.org', + uids: [ + { + id: '01F4W41TMN7NBXBA0PXJMPB7GF', + atype: 1, + ext: { + third: '01F4W41TMN7NBXBA0PXJMPB7GF' + } + } + ] + } + ] } ]; + const bidderRequest = { + 'bidderCode': 'adriver', + 'auctionId': '2cdbf766-c37e-464c-a924-d8cf2a2f7ed2', + 'bidderRequestId': '10415226a1f2ac', + 'bids': [ + { + 'bidder': 'adriver', + 'params': { + 'siteid': '216200', + 'bidfloor': 1.33, + 'placementId': 'test1' + }, + 'auctionId': '2cdbf766-c37e-464c-a924-d8cf2a2f7ed2', + 'floorData': { + 'skipped': false, + 'skipRate': 5, + 'modelVersion': 'BlackBerryZap', + 'location': 'setConfig' + }, + 'userId': { + 'id5id': { + 'uid': 'ID5-ZHMO7vyrzH4ggO1TVF8lZ31h77BjNP6pLgMwIrhvtw!ID5*wP-eG3RLeJjkl1O5yeOMcf3Ksrsq1OeqM5nQZLgPvOMAACaMv9QnPWzdhdbFYu3r', + 'ext': { + 'linkType': 2, + 'abTestingControlGroup': false + } + }, + 'sharedid': { + 'id': '01F4W41TMN7NBXBA0PXJMPB7GF', + 'third': '01F4W41TMN7NBXBA0PXJMPB7GF' + } + }, + 'userIdAsEids': [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'ID5-ZHMO7vyrzH4ggO1TVF8lZ31h77BjNP6pLgMwIrhvtw!ID5*wP-eG3RLeJjkl1O5yeOMcf3Ksrsq1OeqM5nQZLgPvOMAACaMv9QnPWzdhdbFYu3r', + 'atype': 1, + 'ext': { + 'linkType': 2, + 'abTestingControlGroup': false + } + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01F4W41TMN7NBXBA0PXJMPB7GF', + 'atype': 1, + 'ext': { + 'third': '01F4W41TMN7NBXBA0PXJMPB7GF' + } + } + ] + } + ], + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 600, + 500 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-51545-0', + 'transactionId': '01dfccdf-70d0-461f-b284-9132877ebe02', + 'sizes': [ + [ + 300, + 250 + ], + [ + 600, + 500 + ] + ], + 'bidId': '2794d8415635b3', + 'bidderRequestId': '10415226a1f2ac', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1622465003758, + 'timeout': 1000, + 'refererInfo': { + 'referer': 'http://localhost:9999/integrationExamples/gpt/adUnitFloors.html', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://localhost:9999/integrationExamples/gpt/adUnitFloors.html' + ], + 'canonicalUrl': null + }, + 'start': 1622465003762 + }; + let floorTestData = { 'currency': 'USD', 'floor': floor }; + bidRequests[0].getFloor = _ => { return floorTestData; }; @@ -79,6 +243,14 @@ describe('adriverAdapter', function () { expect(payload.cur).to.exist; }); + it('should exist timeout', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.tmax).to.exist; + expect(payload.tmax).to.equal(1000); + }); + it('should exist at', function () { const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); @@ -225,7 +397,34 @@ describe('adriverAdapter', function () { bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', auctionId: '1d1a030790a475', - transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843', + userIdAsEids: [ + { + source: 'id5-sync.com', + uids: [ + { + id: '', + atype: 1, + ext: { + linkType: 0, + abTestingControlGroup: true + } + } + ] + }, + { + source: 'sharedid.org', + uids: [ + { + id: '01F4W41TMN7NBXBA0PXJMPB7GF', + atype: 1, + ext: { + third: '01F4W41TMN7NBXBA0PXJMPB7GF' + } + } + ] + } + ] } ]; @@ -320,4 +519,66 @@ describe('adriverAdapter', function () { expect(payload.imp[0].bidfloorcur).to.equal('RUB'); }); }); + + describe('user ids', function () { + let bidRequests = [ + { + bidder: 'adriver', + params: { + placementId: '55:test_placement', + siteid: 'testSiteID', + dealid: 'dealidTest', + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600], [300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843', + userIdAsEids: [ + { + source: 'id5-sync.com', + uids: [ + { + id: '', + atype: 1, + ext: { + linkType: 0, + abTestingControlGroup: true + } + } + ] + }, + { + source: 'sharedid.org', + uids: [ + { + id: '01F4W41TMN7NBXBA0PXJMPB7GF', + atype: 1, + ext: { + third: '01F4W41TMN7NBXBA0PXJMPB7GF' + } + } + ] + } + ] + } + ]; + + it('user id id5-sync.com', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + + expect(payload.user.ext.eids[0].source).to.equal('id5-sync.com'); + expect(payload.user.ext.eids[0].uids[0].id).to.equal(''); + }); + + it('user id sharedid.org', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + + expect(payload.user.ext.eids[1].source).to.equal('sharedid.org'); + expect(payload.user.ext.eids[1].uids[0].id).to.equal('01F4W41TMN7NBXBA0PXJMPB7GF'); + }); + }); }); diff --git a/test/spec/modules/adtargetBidAdapter_spec.js b/test/spec/modules/adtargetBidAdapter_spec.js index 5a867e7dd52..d1221d24022 100644 --- a/test/spec/modules/adtargetBidAdapter_spec.js +++ b/test/spec/modules/adtargetBidAdapter_spec.js @@ -45,7 +45,8 @@ const SERVER_VIDEO_RESPONSE = { 'height': 480, 'cur': 'USD', 'width': 640, - 'cpm': 0.9 + 'cpm': 0.9, + 'adomain': ['a.com'] }] }; const SERVER_DISPLAY_RESPONSE = { @@ -107,7 +108,10 @@ const videoEqResponse = [{ height: 480, width: 640, ttl: 300, - cpm: 0.9 + cpm: 0.9, + meta: { + advertiserDomains: ['a.com'] + } }]; const displayEqResponse = [{ @@ -120,7 +124,10 @@ const displayEqResponse = [{ height: 250, width: 300, ttl: 300, - cpm: 0.9 + cpm: 0.9, + meta: { + advertiserDomains: [] + } }]; describe('adtargetBidAdapter', () => { diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index e6916997133..a004d888268 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -17,6 +17,7 @@ const aliasEP = { mediafuse: 'https://ghb.hbmp.mediafuse.com/v2/auction/', navelix: 'https://ghb.hb.navelix.com/v2/auction/', bidsxchange: 'https://ghb.hbd.bidsxchange.com/v2/auction/', + streamkey: 'https://ghb.hb.streamkey.net/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; @@ -80,9 +81,9 @@ const SERVER_VIDEO_RESPONSE = { 'height': 480, 'cur': 'USD', 'width': 640, - 'cpm': 0.9 - } - ] + 'cpm': 0.9, + 'adomain': ['a.com'] + }] }; const SERVER_OUSTREAM_VIDEO_RESPONSE = SERVER_VIDEO_RESPONSE; const SERVER_DISPLAY_RESPONSE = { @@ -163,7 +164,10 @@ const videoEqResponse = [{ height: 480, width: 640, ttl: 300, - cpm: 0.9 + cpm: 0.9, + meta: { + advertiserDomains: ['a.com'] + } }]; const displayEqResponse = [{ @@ -177,7 +181,11 @@ const displayEqResponse = [{ height: 250, width: 300, ttl: 300, - cpm: 0.9 + cpm: 0.9, + meta: { + advertiserDomains: [] + } + }]; describe('adtelligentBidAdapter', () => { diff --git a/test/spec/modules/adtelligentIdSystem_spec.js b/test/spec/modules/adtelligentIdSystem_spec.js new file mode 100644 index 00000000000..f3c7262c67a --- /dev/null +++ b/test/spec/modules/adtelligentIdSystem_spec.js @@ -0,0 +1,30 @@ +import { adtelligentIdModule } from 'modules/adtelligentIdSystem' +import * as ajaxLib from 'src/ajax.js'; + +const adtUserIdRemoteResponse = { u: 'test1' }; +const adtUserIdLocalResponse = 'test2'; + +describe('AdtelligentId module', function () { + it('gets remote id', function () { + const ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { + return (url, cbObj) => { + cbObj.success(JSON.stringify(adtUserIdRemoteResponse)) + } + }); + const moduleIdCallbackResponse = adtelligentIdModule.getId(); + moduleIdCallbackResponse.callback((id) => { + expect(id).to.equal(adtUserIdRemoteResponse.u) + }) + ajaxBuilderStub.restore(); + }) + it('gets id from page context', function () { + window.adtDmp = { + ready: true, + getUID() { + return adtUserIdLocalResponse; + } + } + const moduleIdResponse = adtelligentIdModule.getId(); + assert.deepEqual(moduleIdResponse, { id: adtUserIdLocalResponse }); + }) +}) diff --git a/test/spec/modules/adtrueBidAdapter_spec.js b/test/spec/modules/adtrueBidAdapter_spec.js index 8e1c872d460..b499d077a3c 100644 --- a/test/spec/modules/adtrueBidAdapter_spec.js +++ b/test/spec/modules/adtrueBidAdapter_spec.js @@ -21,7 +21,7 @@ describe('AdTrueBidAdapter', function () { params: { publisherId: '1212', zoneId: '21423', - reserve: 0.2 + reserve: 0 }, placementCode: 'adunit-code-1', sizes: [[300, 250]], diff --git a/test/spec/modules/aduptechBidAdapter_spec.js b/test/spec/modules/aduptechBidAdapter_spec.js index 1e39e0cfc8b..362cd3e506a 100644 --- a/test/spec/modules/aduptechBidAdapter_spec.js +++ b/test/spec/modules/aduptechBidAdapter_spec.js @@ -1,180 +1,280 @@ import { expect } from 'chai'; import { BIDDER_CODE, - PUBLISHER_PLACEHOLDER, - ENDPOINT_URL, ENDPOINT_METHOD, - spec, - extractGdprFromBidderRequest, - extractParamsFromBidRequest, - extractSizesFromBidRequest, - extractTopWindowReferrerFromBidRequest, - extractTopWindowUrlFromBidRequest + internal, + spec } from '../../../modules/aduptechBidAdapter.js'; +import { config } from '../../../src/config.js'; +import * as utils from '../../../src/utils.js'; +import { BANNER, NATIVE } from '../../../src/mediaTypes.js' import { newBidder } from '../../../src/adapters/bidderFactory.js'; describe('AduptechBidAdapter', () => { - describe('extractGdprFromBidderRequest', () => { - it('should handle empty bidder request', () => { - const bidderRequest = null; - expect(extractGdprFromBidderRequest(bidderRequest)).to.be.null; - }); + describe('internal', () => { + describe('extractGdpr', () => { + it('should handle empty bidderRequest', () => { + expect(internal.extractGdpr(null)).to.be.null; + expect(internal.extractGdpr({})).to.be.null; + }); - it('should handle missing gdprConsent in bidder request', () => { - const bidderRequest = {}; - expect(extractGdprFromBidderRequest(bidderRequest)).to.be.null; - }); + it('should extract bidderRequest.gdprConsent', () => { + const bidderRequest = { + gdprConsent: { + consentString: 'consentString', + gdprApplies: false + } + }; - it('should handle gdprConsent in bidder request', () => { - const bidderRequest = { - gdprConsent: { - consentString: 'consentString', - gdprApplies: true - } - }; + expect(internal.extractGdpr(bidderRequest)).to.deep.equal({ + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: bidderRequest.gdprConsent.gdprApplies + }); + }); + + it('should handle missing bidderRequest.gdprConsent.gdprApplies', () => { + const bidderRequest = { + gdprConsent: { + consentString: 'consentString' + } + }; - expect(extractGdprFromBidderRequest(bidderRequest)).to.deep.equal({ - consentString: bidderRequest.gdprConsent.consentString, - consentRequired: true + expect(internal.extractGdpr(bidderRequest)).to.deep.equal({ + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: true + }); }); - }); - }); - describe('extractParamsFromBidRequest', () => { - it('should handle empty bid request', () => { - const bidRequest = null; - expect(extractParamsFromBidRequest(bidRequest)).to.be.null; - }); + it('should handle invalid bidderRequest.gdprConsent.gdprApplies', () => { + const bidderRequest = { + gdprConsent: { + consentString: 'consentString', + gdprApplies: 'foobar' + } + }; - it('should handle missing params in bid request', () => { - const bidRequest = {}; - expect(extractParamsFromBidRequest(bidRequest)).to.be.null; + expect(internal.extractGdpr(bidderRequest)).to.deep.equal({ + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: true + }); + }); }); - it('should handle params in bid request', () => { - const bidRequest = { - params: { - foo: '123', - bar: 456 - } - }; - expect(extractParamsFromBidRequest(bidRequest)).to.deep.equal(bidRequest.params); - }); - }); + describe('extractPageUrl', () => { + let origPageUrl; - describe('extractSizesFromBidRequest', () => { - it('should handle empty bid request', () => { - const bidRequest = null; - expect(extractSizesFromBidRequest(bidRequest)).to.deep.equal([]); - }); + beforeEach(() => { + // remember original pageUrl in config + origPageUrl = config.getConfig('pageUrl'); - it('should handle missing sizes in bid request', () => { - const bidRequest = {}; - expect(extractSizesFromBidRequest(bidRequest)).to.deep.equal([]); - }); + // unset pageUrl in config + config.setConfig({ pageUrl: null }); + }); + + afterEach(() => { + // set original pageUrl to config + config.setConfig({ pageUrl: origPageUrl }); + }); + + it('should handle empty or missing data', () => { + expect(internal.extractPageUrl(null)).to.equal(utils.getWindowTop().location.href); + expect(internal.extractPageUrl({})).to.equal(utils.getWindowTop().location.href); + expect(internal.extractPageUrl({ refererInfo: {} })).to.equal(utils.getWindowTop().location.href); + expect(internal.extractPageUrl({ refererInfo: { canonicalUrl: null } })).to.equal(utils.getWindowTop().location.href); + expect(internal.extractPageUrl({ refererInfo: { canonicalUrl: '' } })).to.equal(utils.getWindowTop().location.href); + }); + + it('should use "pageUrl" from config', () => { + config.setConfig({ pageUrl: 'http://page.url' }); - it('should handle sizes in bid request', () => { - const bidRequest = { - mediaTypes: { - banner: { - sizes: [[12, 34], [56, 78]] + expect(internal.extractPageUrl({})).to.equal(config.getConfig('pageUrl')); + }); + + it('should use bidderRequest.refererInfo.canonicalUrl', () => { + const bidderRequest = { + refererInfo: { + canonicalUrl: 'http://canonical.url' } - } - }; - expect(extractSizesFromBidRequest(bidRequest)).to.deep.equal(bidRequest.mediaTypes.banner.sizes); - }); + }; - it('should handle sizes in bid request (backward compatibility)', () => { - const bidRequest = { - sizes: [[12, 34], [56, 78]] - }; - expect(extractSizesFromBidRequest(bidRequest)).to.deep.equal(bidRequest.sizes); - }); + expect(internal.extractPageUrl(bidderRequest)).to.equal(bidderRequest.refererInfo.canonicalUrl); + }); - it('should prefer sizes in mediaTypes.banner', () => { - const bidRequest = { - sizes: [[12, 34]], - mediaTypes: { - banner: { - sizes: [[56, 78]] + it('should prefer bidderRequest.refererInfo.canonicalUrl over "pageUrl" from config', () => { + const bidderRequest = { + refererInfo: { + canonicalUrl: 'http://canonical.url' } - } - }; - expect(extractSizesFromBidRequest(bidRequest)).to.deep.equal(bidRequest.mediaTypes.banner.sizes); - }); - }); + }; - describe('extractTopWindowReferrerFromBidRequest', () => { - it('should use fallback if bid request is empty', () => { - const bidRequest = null; - expect(extractTopWindowReferrerFromBidRequest(bidRequest)).to.equal(window.top.document.referrer); - }); + config.setConfig({ pageUrl: 'http://page.url' }); - it('should use fallback if refererInfo in bid request is missing', () => { - const bidRequest = {}; - expect(extractTopWindowReferrerFromBidRequest(bidRequest)).to.equal(window.top.document.referrer); + expect(internal.extractPageUrl(bidderRequest)).to.equal(bidderRequest.refererInfo.canonicalUrl); + }); }); - it('should use fallback if refererInfo.referer in bid request is missing', () => { - const bidRequest = { - refererInfo: {} - }; - expect(extractTopWindowReferrerFromBidRequest(bidRequest)).to.equal(window.top.document.referrer); - }); + describe('extractReferrer', () => { + it('should handle empty or missing data', () => { + expect(internal.extractReferrer(null)).to.equal(utils.getWindowTop().document.referrer); + expect(internal.extractReferrer({})).to.equal(utils.getWindowTop().document.referrer); + expect(internal.extractReferrer({ refererInfo: {} })).to.equal(utils.getWindowTop().document.referrer); + expect(internal.extractReferrer({ refererInfo: { referer: null } })).to.equal(utils.getWindowTop().document.referrer); + expect(internal.extractReferrer({ refererInfo: { referer: '' } })).to.equal(utils.getWindowTop().document.referrer); + }); - it('should use fallback if refererInfo.referer in bid request is empty', () => { - const bidRequest = { - refererInfo: { - referer: '' - } - }; - expect(extractTopWindowReferrerFromBidRequest(bidRequest)).to.equal(window.top.document.referrer); - }); + it('hould use bidderRequest.refererInfo.referer', () => { + const bidderRequest = { + refererInfo: { + referer: 'foobar' + } + }; - it('should use refererInfo.referer from bid request ', () => { - const bidRequest = { - refererInfo: { - referer: 'foobar' - } - }; - expect(extractTopWindowReferrerFromBidRequest(bidRequest)).to.equal(bidRequest.refererInfo.referer); + expect(internal.extractReferrer(bidderRequest)).to.equal(bidderRequest.refererInfo.referer); + }); }); - }); - describe('extractTopWindowUrlFromBidRequest', () => { - it('should use fallback if bid request is empty', () => { - const bidRequest = null; - expect(extractTopWindowUrlFromBidRequest(bidRequest)).to.equal(window.top.location.href); + describe('extractParams', () => { + it('should handle empty bidRequest', () => { + expect(internal.extractParams(null)).to.be.null; + expect(internal.extractParams({})).to.be.null; + }); + + it('should extract bidRequest.params', () => { + const bidRequest = { + params: { + foo: '123', + bar: 456 + } + }; + expect(internal.extractParams(bidRequest)).to.deep.equal(bidRequest.params); + }); }); - it('should use fallback if refererInfo in bid request is missing', () => { - const bidRequest = {}; - expect(extractTopWindowUrlFromBidRequest(bidRequest)).to.equal(window.top.location.href); + describe('extractBannerConfig', () => { + it('should handle empty bidRequest', () => { + expect(internal.extractBannerConfig(null)).to.be.null; + expect(internal.extractBannerConfig({})).to.be.null; + }); + + it('should extract bidRequest.mediaTypes.banner', () => { + const bidRequest = { + mediaTypes: { + banner: { + sizes: [[12, 34], [56, 78]] + } + } + }; + expect(internal.extractBannerConfig(bidRequest)).to.deep.equal(bidRequest.mediaTypes.banner); + }); + + it('should extract bidRequest.sizes (backward compatibility)', () => { + const bidRequest = { + sizes: [[12, 34], [56, 78]] + }; + + expect(internal.extractBannerConfig(bidRequest)).to.deep.equal({sizes: bidRequest.sizes}); + }); }); - it('should use fallback if refererInfo.canonicalUrl in bid request is missing', () => { - const bidRequest = { - refererInfo: {} - }; - expect(extractTopWindowUrlFromBidRequest(bidRequest)).to.equal(window.top.location.href); + describe('extractNativeConfig', () => { + it('should handle empty bidRequest', () => { + expect(internal.extractNativeConfig(null)).to.be.null; + expect(internal.extractNativeConfig({})).to.be.null; + }); + + it('should extract bidRequest.mediaTypes.native', () => { + const bidRequest = { + mediaTypes: { + native: { + image: { + required: true + }, + title: { + required: true + } + } + } + }; + + expect(internal.extractNativeConfig(bidRequest)).to.deep.equal(bidRequest.mediaTypes.native); + }); }); - it('should use fallback if refererInfo.canonicalUrl in bid request is empty', () => { - const bidRequest = { - refererInfo: { - canonicalUrl: '' - } - }; - expect(extractTopWindowUrlFromBidRequest(bidRequest)).to.equal(window.top.location.href); + describe('groupBidRequestsByPublisher', () => { + it('should handle empty bidRequests', () => { + expect(internal.groupBidRequestsByPublisher(null)).to.deep.equal({}); + expect(internal.groupBidRequestsByPublisher([])).to.deep.equal({}) + }); + + it('should group given bidRequests by params.publisher', () => { + const bidRequests = [ + { + mediaTypes: { + banner: { + sizes: [[100, 100]] + } + }, + params: { + publisher: 'publisher1', + placement: '1111' + } + }, + { + mediaTypes: { + banner: { + sizes: [[200, 200]] + } + }, + params: { + publisher: 'publisher2', + placement: '2222' + } + }, + { + mediaTypes: { + banner: { + sizes: [[300, 300]] + } + }, + params: { + publisher: 'publisher3', + placement: '3333' + } + }, + { + mediaTypes: { + banner: { + sizes: [[400, 400]] + } + }, + params: { + publisher: 'publisher1', + placement: '4444' + } + } + ]; + + expect(internal.groupBidRequestsByPublisher(bidRequests)).to.deep.equal({ + publisher1: [ + bidRequests[0], + bidRequests[3] + ], + publisher2: [ + bidRequests[1], + ], + publisher3: [ + bidRequests[2], + ], + }); + }); }); - it('should use refererInfo.canonicalUrl from bid request ', () => { - const bidRequest = { - refererInfo: { - canonicalUrl: 'foobar' - } - }; - expect(extractTopWindowUrlFromBidRequest(bidRequest)).to.equal(bidRequest.refererInfo.canonicalUrl); + describe('buildEndpointUrl', () => { + it('should build endpoint url based on given publisher code', () => { + expect(internal.buildEndpointUrl(1234)).to.be.equal('https://rtb.d.adup-tech.com/prebid/1234_bid'); + expect(internal.buildEndpointUrl('foobar')).to.be.equal('https://rtb.d.adup-tech.com/prebid/foobar_bid'); + expect(internal.buildEndpointUrl('foo bar')).to.be.equal('https://rtb.d.adup-tech.com/prebid/foo%20bar_bid'); + }); }); }); @@ -185,42 +285,31 @@ describe('AduptechBidAdapter', () => { adapter = newBidder(spec); }); - describe('inherited functions', () => { - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); + describe('code', () => { + it('should be correct', () => { + expect(adapter.getSpec().code).to.equal(BIDDER_CODE); }); }); - describe('isBidRequestValid', () => { - it('should return true when necessary information is given', () => { - expect(spec.isBidRequestValid({ - mediaTypes: { - banner: { - sizes: [[100, 200]] - } - }, - params: { - publisher: 'test', - placement: '1234' - } - })).to.be.true; + describe('supportedMediaTypes', () => { + it('should be correct', () => { + expect(adapter.getSpec().supportedMediaTypes).to.deep.equal([BANNER, NATIVE]); }); + }); - it('should return true when necessary information is given (backward compatibility)', () => { - expect(spec.isBidRequestValid({ - sizes: [[100, 200]], - params: { - publisher: 'test', - placement: '1234' - } - })).to.be.true; + describe('inherited functions', () => { + it('should exist and be a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); }); + }); - it('should return false on empty bid', () => { + describe('isBidRequestValid', () => { + it('should be false on empty bid', () => { + expect(spec.isBidRequestValid(null)).to.be.false; expect(spec.isBidRequestValid({})).to.be.false; }); - it('should return false on missing sizes', () => { + it('should be false if mediaTypes.banner and mediaTypes.native is missing', () => { expect(spec.isBidRequestValid({ params: { publisher: 'test', @@ -229,63 +318,65 @@ describe('AduptechBidAdapter', () => { })).to.be.false; }); - it('should return false on empty sizes', () => { + it('should be false if params missing', () => { expect(spec.isBidRequestValid({ mediaTypes: { banner: { - sizes: [] + sizes: [[100, 200]] } }, - params: { - publisher: 'test', - placement: '1234' - } })).to.be.false; }); - it('should return false on empty sizes (backward compatibility)', () => { + it('should be false if params is invalid', () => { expect(spec.isBidRequestValid({ - sizes: [], - params: { - publisher: 'test', - placement: '1234' - } + mediaTypes: { + banner: { + sizes: [[100, 200]] + } + }, + params: 'bar' })).to.be.false; }); - it('should return false on missing params', () => { + it('should be false if params is empty', () => { expect(spec.isBidRequestValid({ mediaTypes: { banner: { sizes: [[100, 200]] } }, + params: {} })).to.be.false; }); - it('should return false on invalid params', () => { + it('should be false if params.publisher is missing', () => { expect(spec.isBidRequestValid({ mediaTypes: { banner: { sizes: [[100, 200]] } }, - params: 'bar' + params: { + placement: '1234' + } })).to.be.false; }); - it('should return false on empty params', () => { + it('should be false if params.placement is missing', () => { expect(spec.isBidRequestValid({ mediaTypes: { banner: { sizes: [[100, 200]] } }, - params: {} + params: { + publisher: 'test' + } })).to.be.false; }); - it('should return false on missing publisher', () => { + it('should be true if mediaTypes.banner is given', () => { expect(spec.isBidRequestValid({ mediaTypes: { banner: { @@ -293,34 +384,62 @@ describe('AduptechBidAdapter', () => { } }, params: { + publisher: 'test', placement: '1234' } - })).to.be.false; + })).to.be.true; }); - it('should return false on missing placement', () => { + it('should be true if mediaTypes.native is given', () => { expect(spec.isBidRequestValid({ mediaTypes: { - banner: { - sizes: [[100, 200]] + native: { + image: { + required: true + }, + title: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + } } }, params: { - publisher: 'test' + publisher: 'test', + placement: '1234' } - })).to.be.false; + })).to.be.true; }); }); describe('buildRequests', () => { - it('should send one bid request per ad unit to the endpoint via POST', () => { - const bidRequests = [ + it('should handle empty validBidRequests', () => { + expect(spec.buildRequests(null)).to.deep.equal([]); + expect(spec.buildRequests([])).to.deep.equal([]); + }); + + it('should build one request per publisher', () => { + const bidderRequest = { + auctionId: 'auctionId123', + refererInfo: { + canonicalUrl: 'http://crazy.canonical.url', + referer: 'http://crazy.referer.url' + }, + gdprConsent: { + consentString: 'consentString123', + gdprApplies: true + } + }; + + const validBidRequests = [ { - bidder: BIDDER_CODE, bidId: 'bidId1', adUnitCode: 'adUnitCode1', transactionId: 'transactionId1', - auctionId: 'auctionId1', mediaTypes: { banner: { sizes: [[100, 200], [300, 400]] @@ -332,170 +451,197 @@ describe('AduptechBidAdapter', () => { } }, { - bidder: BIDDER_CODE, bidId: 'bidId2', adUnitCode: 'adUnitCode2', transactionId: 'transactionId2', - auctionId: 'auctionId2', mediaTypes: { banner: { - sizes: [[500, 600]] + sizes: [[100, 200]] } }, params: { - publisher: 'publisher2', + publisher: 'publisher1', placement: 'placement2' } - } - ]; - - const result = spec.buildRequests(bidRequests); - expect(result.length).to.equal(2); - - expect(result[0].url).to.equal(ENDPOINT_URL.replace(PUBLISHER_PLACEHOLDER, bidRequests[0].params.publisher)); - expect(result[0].method).to.equal(ENDPOINT_METHOD); - expect(result[0].data).to.deep.equal({ - bidId: bidRequests[0].bidId, - auctionId: bidRequests[0].auctionId, - transactionId: bidRequests[0].transactionId, - adUnitCode: bidRequests[0].adUnitCode, - pageUrl: extractTopWindowUrlFromBidRequest(bidRequests[0]), - referrer: extractTopWindowReferrerFromBidRequest(bidRequests[0]), - sizes: extractSizesFromBidRequest(bidRequests[0]), - params: extractParamsFromBidRequest(bidRequests[0]), - gdpr: null - }); - - expect(result[1].url).to.equal(ENDPOINT_URL.replace(PUBLISHER_PLACEHOLDER, bidRequests[1].params.publisher)); - expect(result[1].method).to.equal(ENDPOINT_METHOD); - expect(result[1].data).to.deep.equal({ - bidId: bidRequests[1].bidId, - auctionId: bidRequests[1].auctionId, - transactionId: bidRequests[1].transactionId, - adUnitCode: bidRequests[1].adUnitCode, - pageUrl: extractTopWindowUrlFromBidRequest(bidRequests[1]), - referrer: extractTopWindowReferrerFromBidRequest(bidRequests[1]), - sizes: extractSizesFromBidRequest(bidRequests[1]), - params: extractParamsFromBidRequest(bidRequests[1]), - gdpr: null - }); - }); - - it('should pass gdpr informations', () => { - const bidderRequest = { - gdprConsent: { - consentString: 'consentString', - gdprApplies: true - } - }; - - const bidRequests = [ + }, { - bidder: BIDDER_CODE, bidId: 'bidId3', adUnitCode: 'adUnitCode3', transactionId: 'transactionId3', - auctionId: 'auctionId3', mediaTypes: { - banner: { - sizes: [[100, 200], [300, 400]] + native: { + image: { + required: true + }, + title: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + } } }, params: { - publisher: 'publisher3', + publisher: 'publisher2', placement: 'placement3' } } ]; - const result = spec.buildRequests(bidRequests, bidderRequest); - expect(result.length).to.equal(1); - expect(result[0].data.gdpr).to.deep.equal(extractGdprFromBidderRequest(bidderRequest)); - }); - - it('should encode publisher param in endpoint url', () => { - const bidRequests = [ + expect(spec.buildRequests(validBidRequests, bidderRequest)).to.deep.equal([ { - bidder: BIDDER_CODE, - bidId: 'bidId1', - adUnitCode: 'adUnitCode1', - transactionId: 'transactionId1', - auctionId: 'auctionId1', - mediaTypes: { - banner: { - sizes: [[100, 200]] - } - }, - params: { - publisher: 'crazy publisher key äÖÜ', - placement: 'placement1' + url: internal.buildEndpointUrl(validBidRequests[0].params.publisher), + method: ENDPOINT_METHOD, + data: { + auctionId: bidderRequest.auctionId, + pageUrl: bidderRequest.refererInfo.canonicalUrl, + referrer: bidderRequest.refererInfo.referer, + gdpr: { + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: bidderRequest.gdprConsent.gdprApplies + }, + imp: [ + { + bidId: validBidRequests[0].bidId, + transactionId: validBidRequests[0].transactionId, + adUnitCode: validBidRequests[0].adUnitCode, + params: validBidRequests[0].params, + banner: validBidRequests[0].mediaTypes.banner + }, + { + bidId: validBidRequests[1].bidId, + transactionId: validBidRequests[1].transactionId, + adUnitCode: validBidRequests[1].adUnitCode, + params: validBidRequests[1].params, + banner: validBidRequests[1].mediaTypes.banner + } + ] } }, - ]; - - const result = spec.buildRequests(bidRequests); - expect(result[0].url).to.equal(ENDPOINT_URL.replace(PUBLISHER_PLACEHOLDER, encodeURIComponent(bidRequests[0].params.publisher))); - }); - - it('should handle empty bidRequests', () => { - expect(spec.buildRequests([])).to.deep.equal([]); - }); - }); - - describe('interpretResponse', () => { - it('should correctly interpret the server response', () => { - const serverResponse = { - body: { - bid: { - bidId: 'bidId1', - price: 0.12, - net: true, - currency: 'EUR', - ttl: 123 - }, - creative: { - id: 'creativeId1', - width: 100, - height: 200, - html: '
Hello World
' - } - } - }; - - const result = spec.interpretResponse(serverResponse); - expect(result).to.deep.equal([ { - requestId: serverResponse.body.bid.bidId, - cpm: serverResponse.body.bid.price, - netRevenue: serverResponse.body.bid.net, - currency: serverResponse.body.bid.currency, - ttl: serverResponse.body.bid.ttl, - creativeId: serverResponse.body.creative.id, - width: serverResponse.body.creative.width, - height: serverResponse.body.creative.height, - ad: serverResponse.body.creative.html + url: internal.buildEndpointUrl(validBidRequests[2].params.publisher), + method: ENDPOINT_METHOD, + data: { + auctionId: bidderRequest.auctionId, + pageUrl: bidderRequest.refererInfo.canonicalUrl, + referrer: bidderRequest.refererInfo.referer, + gdpr: { + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: bidderRequest.gdprConsent.gdprApplies + }, + imp: [ + { + bidId: validBidRequests[2].bidId, + transactionId: validBidRequests[2].transactionId, + adUnitCode: validBidRequests[2].adUnitCode, + params: validBidRequests[2].params, + native: validBidRequests[2].mediaTypes.native + } + ] + } } ]); }); + }); + describe('interpretResponse', () => { it('should handle empty serverResponse', () => { + expect(spec.interpretResponse(null)).to.deep.equal([]); expect(spec.interpretResponse({})).to.deep.equal([]); + expect(spec.interpretResponse({ body: {} })).to.deep.equal([]); + expect(spec.interpretResponse({ body: { bids: [] } })).to.deep.equal([]); }); - it('should handle missing bid', () => { - expect(spec.interpretResponse({ + it('should correctly interpret the server response', () => { + const serverResponse = { body: { - creative: {} + bids: [ + { + bid: { + bidId: 'bidId1', + price: 0.12, + net: true, + currency: 'EUR', + ttl: 123 + }, + creative: { + id: 'creativeId1', + advertiserDomains: ['advertiser1.com', 'advertiser2.org'], + width: 100, + height: 200, + html: '
Hello World
' + } + }, + { + bid: { + bidId: 'bidId2', + price: 0.99, + net: false, + currency: 'USD', + ttl: 465 + }, + creative: { + id: 'creativeId2', + advertiserDomains: ['advertiser3.com'], + native: { + title: 'Ad title', + body: 'Ad description', + displayUrl: 'Ad display url', + clickUrl: 'http://click.url/ad.html', + image: { + url: 'https://image.url/ad.png', + width: 123, + height: 456 + }, + sponsoredBy: 'Ad sponsored by', + impressionTrackers: [ + 'https://impression.tracking.url/1', + 'https://impression.tracking.url/2', + ], + privacyLink: 'https://example.com/privacy', + privacyIcon: 'https://example.com/icon.png' + } + } + }, + null, // should be skipped + {} // should be skipped + ] } - })).to.deep.equal([]); - }); + }; - it('should handle missing creative', () => { - expect(spec.interpretResponse({ - body: { - bid: {} + expect(spec.interpretResponse(serverResponse)).to.deep.equal([ + { + requestId: serverResponse.body.bids[0].bid.bidId, + cpm: serverResponse.body.bids[0].bid.price, + netRevenue: serverResponse.body.bids[0].bid.net, + currency: serverResponse.body.bids[0].bid.currency, + ttl: serverResponse.body.bids[0].bid.ttl, + creativeId: serverResponse.body.bids[0].creative.id, + meta: { + advertiserDomains: serverResponse.body.bids[0].creative.advertiserDomains + }, + mediaType: BANNER, + width: serverResponse.body.bids[0].creative.width, + height: serverResponse.body.bids[0].creative.height, + ad: serverResponse.body.bids[0].creative.html + }, + { + requestId: serverResponse.body.bids[1].bid.bidId, + cpm: serverResponse.body.bids[1].bid.price, + netRevenue: serverResponse.body.bids[1].bid.net, + currency: serverResponse.body.bids[1].bid.currency, + ttl: serverResponse.body.bids[1].bid.ttl, + creativeId: serverResponse.body.bids[1].creative.id, + meta: { + advertiserDomains: serverResponse.body.bids[1].creative.advertiserDomains + }, + mediaType: NATIVE, + native: serverResponse.body.bids[1].creative.native } - })).to.deep.equal([]); + ]); }); }); }); diff --git a/test/spec/modules/advangelistsBidAdapter_spec.js b/test/spec/modules/advangelistsBidAdapter_spec.js index 2b9615fb572..e1cd6977c5d 100755 --- a/test/spec/modules/advangelistsBidAdapter_spec.js +++ b/test/spec/modules/advangelistsBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/advangelistsBidAdapter.js'; -import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { spec } from 'modules/advangelistsBidAdapter'; +import { BANNER, VIDEO } from 'src/mediaTypes'; describe('advangelistsBidAdapter', function () { let bidRequests; @@ -9,7 +9,7 @@ describe('advangelistsBidAdapter', function () { beforeEach(function () { bidRequests = [{'bidder': 'advangelists', 'params': {'pubid': '0cf8d6d643e13d86a5b6374148a4afac', 'placement': 1234}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'f72931e6-2b0e-4e37-a2bc-1ea912141f81', 'sizes': [[300, 250]], 'bidId': '2aa73f571eaf29', 'bidderRequestId': '1bac84515a7af3', 'auctionId': '5dbc60fa-1aa1-41ce-9092-e6bbd4d478f7', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; - bidRequestsVid = [{'bidder': 'advangelists', 'params': {'pubid': '8537f00948fc37cc03c5f0f88e198a76', 'placement': 1234, 'video': {'id': 123, 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream'}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + bidRequestsVid = [{'bidder': 'advangelists', 'params': {'pubid': '8537f00948fc37cc03c5f0f88e198a76', 'placement': 1234}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream', 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; }); describe('spec.isBidRequestValid', function () { @@ -87,7 +87,7 @@ describe('advangelistsBidAdapter', function () { it('should return valid video bid responses', function () { let _mediaTypes = VIDEO; const advangelistsbidreqVid = {'bidRequest': {'mediaTypes': {'video': {'w': 320, 'h': 480}}}}; - const serverResponseVid = {'cur': 'USD', 'id': '25c6ab92aa0e81', 'seatbid': [{'seat': '3', 'bid': [{'crid': '1855', 'h': 480, 'protocol': 2, 'nurl': 'http://nep.advangelists.com/xp/evt?pp=1MO1wiaMhhq7wLRzZZwwwPkJxxKpYEnM5k5MH4qSGm1HR8rp3Nl7vDocvzZzSAvE4pnREL9mQ1kf5PDjk6E8em6DOk7vVrYUH1TYQyqCucd58PFpJNN7h30RXKHHFg3XaLuQ3PKfMuI1qZATBJ6WHcu875y0hqRdiewn0J4JsCYF53M27uwmcV0HnQxARQZZ72mPqrW95U6wgkZljziwKrICM3aBV07TU6YK5R5AyzJRuD6mtrQ2xtHlQ3jXVYKE5bvWFiUQd90t0jOGhPtYBNoOfP7uQ4ZZj4pyucxbr96orHe9PSOn9UpCSWArdx7s8lOfDpwOvbMuyGxynbStDWm38sDgd4bMHnIt762m5VMDNJfiUyX0vWzp05OsufJDVEaWhAM62i40lQZo7mWP4ipoOWLkmlaAzFIMsTcNaHAHiKKqGEOZLkCEhFNM0SLcvgN2HFRULOOIZvusq7TydOKxuXgCS91dLUDxDDDFUK83BFKlMkTxnCzkLbIR1bd9GKcr1TRryOrulyvRWAKAIhEsUzsc5QWFUhmI2dZ1eqnBQJ0c89TaPcnoaP2WipF68UgyiOstf2CBy0M34858tC5PmuQwQYwXscg6zyqDwR0i9MzGH4FkTyU5yeOlPcsA0ht6UcoCdFpHpumDrLUwAaxwGk1Nj8S6YlYYT5wNuTifDGbg22QKXzZBkUARiyVvgPn9nRtXnrd7WmiMYq596rya9RQj7LC0auQW8bHVQLEe49shsZDnAwZTWr4QuYKqgRGZcXteG7RVJe0ryBZezOq11ha9C0Lv0siNVBahOXE35Wzoq4c4BDaGpqvhaKN7pjeWLGlQR04ufWekwxiMWAvjmfgAfexBJ7HfbYNZpq__', 'adid': '61_1855', 'adomain': ['chevrolet.com.ar'], 'price': 2, 'w': 320, 'iurl': 'https://daf37cpxaja7f.cloudfront.net/c61/creative_url_14922301369663_1.png', 'cat': ['IAB2'], 'id': '7f570b40-aca1-4806-8ea8-818ea679c82b_0', 'attr': [], 'impid': '0', 'cid': '61'}]}], 'bidid': '7f570b40-aca1-4806-8ea8-818ea679c82b'} + const serverResponseVid = {'cur': 'USD', 'id': '25c6ab92aa0e81', 'seatbid': [{'seat': '3', 'bid': [{'crid': '1855', 'h': 480, 'protocol': 2, 'nurl': 'http://nep.advangelists.com/xp/evt?pp=1MO1wiaMhhq7wLRzZZwwwPkJxxKpYEnM5k5MH4qSGm1HR8rp3Nl7vDocvzZzSAvE4pnREL9mQ1kf5PDjk6E8em6DOk7vVrYUH1TYQyqCucd58PFpJNN7h30RXKHHFg3XaLuQ3PKfMuI1qZATBJ6WHcu875y0hqRdiewn0J4JsCYF53M27uwmcV0HnQxARQZZ72mPqrW95U6wgkZljziwKrICM3aBV07TU6YK5R5AyzJRuD6mtrQ2xtHlQ3jXVYKE5bvWFiUQd90t0jOGhPtYBNoOfP7uQ4ZZj4pyucxbr96orHe9PSOn9UpCSWArdx7s8lOfDpwOvbMuyGxynbStDWm38sDgd4bMHnIt762m5VMDNJfiUyX0vWzp05OsufJDVEaWhAM62i40lQZo7mWP4ipoOWLkmlaAzFIMsTcNaHAHiKKqGEOZLkCEhFNM0SLcvgN2HFRULOOIZvusq7TydOKxuXgCS91dLUDxDDDFUK83BFKlMkTxnCzkLbIR1bd9GKcr1TRryOrulyvRWAKAIhEsUzsc5QWFUhmI2dZ1eqnBQJ0c89TaPcnoaP2WipF68UgyiOstf2CBy0M34858tC5PmuQwQYwXscg6zyqDwR0i9MzGH4FkTyU5yeOlPcsA0ht6UcoCdFpHpumDrLUwAaxwGk1Nj8S6YlYYT5wNuTifDGbg22QKXzZBkUARiyVvgPn9nRtXnrd7WmiMYq596rya9RQj7LC0auQW8bHVQLEe49shsZDnAwZTWr4QuYKqgRGZcXteG7RVJe0ryBZezOq11ha9C0Lv0siNVBahOXE35Wzoq4c4BDaGpqvhaKN7pjeWLGlQR04ufWekwxiMWAvjmfgAfexBJ7HfbYNZpq__', 'adid': '61_1855', 'adomain': ['chevrolet.com'], 'price': 2, 'w': 320, 'iurl': 'https://daf37cpxaja7f.cloudfront.net/c61/creative_url_14922301369663_1.png', 'cat': ['IAB2'], 'id': '7f570b40-aca1-4806-8ea8-818ea679c82b_0', 'attr': [], 'impid': '0', 'cid': '61'}]}], 'bidid': '7f570b40-aca1-4806-8ea8-818ea679c82b'}; const bidResponseVid = spec.interpretResponse({ body: serverResponseVid }, advangelistsbidreqVid); delete bidResponseVid['vastUrl']; delete bidResponseVid['ad']; @@ -99,6 +99,7 @@ describe('advangelistsBidAdapter', function () { width: serverResponseVid.seatbid[0].bid[0].w, height: serverResponseVid.seatbid[0].bid[0].h, mediaType: 'video', + meta: { 'advertiserDomains': serverResponseVid.seatbid[0].bid[0].adomain }, currency: 'USD', netRevenue: true, ttl: 60 @@ -115,7 +116,7 @@ describe('advangelistsBidAdapter', function () { }; }); - const serverResponse = {'id': '2aa73f571eaf29', 'seatbid': [{'bid': [{'id': '2c5e8a1a84522d', 'impid': '2c5e8a1a84522d', 'price': 0.81, 'adid': 'abcde-12345', 'nurl': '', 'adm': '
', 'adomain': ['advertiserdomain.com'], 'iurl': '', 'cid': 'campaign1', 'crid': 'abcde-12345', 'w': 300, 'h': 250}], 'seat': '19513bcfca8006'}], 'bidid': '19513bcfca8006', 'cur': 'USD', 'w': 300, 'h': 250}; + const serverResponse = {'id': '2aa73f571eaf29', 'seatbid': [{'bid': [{'id': '2c5e8a1a84522d', 'impid': '2c5e8a1a84522d', 'price': 0.81, 'adid': 'abcde-12345', 'nurl': '', 'adm': '
', 'iurl': '', 'cid': 'campaign1', 'crid': 'abcde-12345', 'adomain': ['chevrolet.com'], 'w': 300, 'h': 250}], 'seat': '19513bcfca8006'}], 'bidid': '19513bcfca8006', 'cur': 'USD', 'w': 300, 'h': 250}; const bidResponse = spec.interpretResponse({ body: serverResponse }, advangelistsbidreq); expect(bidResponse).to.deep.equal({ @@ -127,6 +128,7 @@ describe('advangelistsBidAdapter', function () { width: serverResponse.seatbid[0].bid[0].w, height: serverResponse.seatbid[0].bid[0].h, mediaType: 'banner', + meta: { 'advertiserDomains': serverResponse.seatbid[0].bid[0].adomain }, currency: 'USD', netRevenue: true, ttl: 60 diff --git a/test/spec/modules/advenueBidAdapter_spec.js b/test/spec/modules/advenueBidAdapter_spec.js deleted file mode 100644 index 2d7739361b4..00000000000 --- a/test/spec/modules/advenueBidAdapter_spec.js +++ /dev/null @@ -1,107 +0,0 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/advenueBidAdapter.js'; - -describe('AdvenueAdapter', function () { - let bid = { - bidId: '2dd581a2b6281d', - bidder: 'advenue', - bidderRequestId: '145e1d6a7837c9', - params: { - placementId: 123, - traffic: 'banner' - }, - placementCode: 'placement_0', - auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - sizes: [[300, 250]], - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' - }; - - describe('isBidRequestValid', function () { - it('Should return true when placementId can be cast to a number', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; - }); - it('Should return false when placementId is not a number', function () { - bid.params.placementId = 'aaa'; - expect(spec.isBidRequestValid(bid)).to.be.false; - }); - }); - - describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid]); - it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequest).to.exist; - expect(serverRequest.method).to.exist; - expect(serverRequest.url).to.exist; - expect(serverRequest.data).to.exist; - }); - it('Returns POST method', function () { - expect(serverRequest.method).to.equal('POST'); - }); - it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://ssp.advenuemedia.co.uk/?c=o&m=multi'); - }); - it('Returns valid data if array of bids is valid', function () { - let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'secure', 'host', 'page', 'placements'); - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.secure).to.be.within(0, 1); - expect(data.host).to.be.a('string'); - expect(data.page).to.be.a('string'); - let placements = data['placements']; - for (let i = 0; i < placements.length; i++) { - let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'bidId', 'traffic', 'sizes'); - expect(placement.placementId).to.be.a('number'); - expect(placement.bidId).to.be.a('string'); - expect(placement.traffic).to.be.a('string'); - expect(placement.sizes).to.be.an('array'); - } - }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); - }); - describe('interpretResponse', function () { - let resObject = { - body: [ { - requestId: '123', - mediaType: 'banner', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD' - } ] - }; - let serverResponses = spec.interpretResponse(resObject); - it('Returns an array of valid server responses if response object is valid', function () { - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - let dataItem = serverResponses[i]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - expect(dataItem.ad).to.be.a('string'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.mediaType).to.be.a('string'); - } - it('Returns an empty array if invalid response is passed', function () { - serverResponses = spec.interpretResponse('invalid_response'); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - }); - }); -}); diff --git a/test/spec/modules/advertlyBidAdapter_spec.js b/test/spec/modules/advertlyBidAdapter_spec.js deleted file mode 100755 index 7825f11948a..00000000000 --- a/test/spec/modules/advertlyBidAdapter_spec.js +++ /dev/null @@ -1,159 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/advertlyBidAdapter.js'; - -const ENDPOINT = 'https://api.advertly.com/www/admin/plugins/Prebid/getAd.php'; - -describe('The Advertly bidding adapter', function () { - describe('isBidRequestValid', function () { - it('should return false when given an invalid bid', function () { - const bid = { - bidder: 'advertly', - }; - const isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(false); - }); - - it('should return true when given a publisherId in bid', function () { - const bid = { - bidder: 'advertly', - params: { - publisherId: 2 - }, - }; - const isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(true); - }); - }); - - describe('buildRequests', function () { - const bidRequests = [{ - 'bidder': 'advertly', - 'params': { - 'publisherId': 2 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ] - }]; - - const request = spec.buildRequests(bidRequests); - - it('sends bid request to our endpoint via POST', function () { - expect(request.method).to.equal('POST'); - }); - - it('check endpoint url', function () { - expect(request.url).to.equal(ENDPOINT) - }); - - it('sets the proper banner object', function () { - expect(bidRequests[0].params.publisherId).to.equal(2); - }) - }); - const response = { - body: [ - { - 'requestId': '2ee937f15015c6', - 'cpm': '0.2000', - 'width': 300, - 'height': 600, - 'creativeId': '4', - 'currency': 'USD', - 'netRevenue': true, - 'ad': 'ads.html', - 'mediaType': 'banner' - }, - { - 'requestId': '3e1af92622bdc', - 'cpm': '0.2000', - 'creativeId': '4', - 'context': 'outstream', - 'currency': 'USD', - 'netRevenue': true, - 'vastUrl': 'tezt.xml', - 'width': 640, - 'height': 480, - 'mediaType': 'video' - }] - }; - - const request = [ - { - 'bidder': 'advertly', - 'params': { - 'publisherId': 2 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [300, 600] - ] - } - }, - 'bidId': '2ee937f15015c6', - 'src': 'client', - }, - { - 'bidder': 'advertly', - 'params': { - 'publisherId': 2 - }, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'playerSize': [ - [640, 480] - ] - } - }, - 'bidId': '3e1af92622bdc', - 'src': 'client', - } - ]; - - describe('interpretResponse', function () { - it('return empty array when no ad found', function () { - const response = {}; - const request = { bidRequests: [] }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(0); - }); - - it('check response for banner and video', function () { - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(2); - expect(bids[0].requestId).to.equal('2ee937f15015c6'); - expect(bids[0].cpm).to.equal('0.2000'); - expect(bids[1].cpm).to.equal('0.2000'); - expect(bids[0].width).to.equal(300); - expect(bids[0].height).to.equal(600); - expect(bids[1].vastUrl).to.not.equal(''); - expect(bids[0].ad).to.not.equal(''); - expect(bids[1].adResponse).to.not.equal(''); - expect(bids[1].renderer).not.to.be.an('undefined'); - }); - }); - - describe('On winning bid', function () { - const bids = spec.interpretResponse(response, request); - spec.onBidWon(bids); - }); - - describe('On bid Time out', function () { - const bids = spec.interpretResponse(response, request); - spec.onTimeout(bids); - }); - - describe('user sync', function () { - it('to check the user sync iframe', function () { - let syncs = spec.getUserSyncs({ - iframeEnabled: true - }); - expect(syncs).to.not.be.an('undefined'); - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('iframe'); - }); - }); -}); diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index 551d50b60e7..7721295572c 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -1,653 +1,902 @@ -import {expect} from 'chai'; +// jshint esversion: 6, es3: false, node: true +import {assert} from 'chai'; import {spec} from 'modules/adxcgBidAdapter.js'; -import {deepClone, parseUrl} from 'src/utils.js'; -import * as utils from '../../../src/utils.js'; - -describe('AdxcgAdapter', function () { - let bidBanner = { - bidder: 'adxcg', - params: { - adzoneid: '1' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [640, 360], - [1, 1] - ] - } - }, - bidId: '84ab500420319d', - bidderRequestId: '7101db09af0db2', - auctionId: '1d1a030790a475' - }; - - let bidVideo = { - bidder: 'adxcg', - params: { - adzoneid: '20', - video: { - api: [2], - maxduration: 30 - } - }, - mediaTypes: { - video: { - context: 'instream', - playerSize: [[640, 480]], - protocols: [1, 2], - mimes: ['video/mp4'], - } - }, - adUnitCode: 'adunit-code', - bidId: '84ab500420319d', - bidderRequestId: '7101db09af0db2', - auctionId: '1d1a030790a475' - }; - - let bidNative = { - bidder: 'adxcg', - params: { - adzoneid: '2379' - }, - mediaTypes: { - native: { - image: { - sendId: false, - required: true, - sizes: [80, 80] - }, - title: { - required: true, - len: 75 - }, - body: { - required: true, - len: 200 - }, - sponsoredBy: { - required: false, - len: 20 - } - } - }, - adUnitCode: 'adunit-code', - bidId: '84ab500420319d', - bidderRequestId: '7101db09af0db2', - auctionId: '1d1a030790a475' - }; +import {config} from 'src/config.js'; +import {createEidsArray} from 'modules/userId/eids.js'; +const utils = require('src/utils'); - describe('isBidRequestValid', function () { - it('should return true when required params found bidNative', function () { - expect(spec.isBidRequestValid(bidNative)).to.equal(true); - }); +describe('Adxcg adapter', function () { + let bids = []; - it('should return true when required params found bidVideo', function () { - expect(spec.isBidRequestValid(bidVideo)).to.equal(true); - }); + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'adxcg', + 'params': { + 'adzoneid': '19910113' + } + }; - it('should return true when required params found bidBanner', function () { - expect(spec.isBidRequestValid(bidBanner)).to.equal(true); - }); + it('should return true when required params found', function () { + assert(spec.isBidRequestValid(bid)); - it('should return false when required params not found', function () { - expect(spec.isBidRequestValid({})).to.be.false; + bid.params = { + adzoneid: 4332, + }; + assert(spec.isBidRequestValid(bid)); }); - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bidBanner); - delete bid.params; + it('should return false when required params are missing', function () { bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); + assert.isFalse(spec.isBidRequestValid(bid)); - it('should return true when required video params not found', function () { - const simpleVideo = JSON.parse(JSON.stringify(bidVideo)); - simpleVideo.params.adzoneid = 123; - expect(spec.isBidRequestValid(simpleVideo)).to.be.false; + bid.params = { + mname: 'some-placement' + }; + assert.isFalse(spec.isBidRequestValid(bid)); + + bid.params = { + inv: 1234 + }; + assert.isFalse(spec.isBidRequestValid(bid)); }); }); - describe('request function http', function () { - it('creates a valid adxcg request url bidBanner', function () { - let request = spec.buildRequests([bidBanner]); - expect(request).to.exist; - expect(request.method).to.equal('GET'); - let parsedRequestUrl = parseUrl(request.url); - expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); - expect(parsedRequestUrl.pathname).to.equal('/get/adi'); - - let query = parsedRequestUrl.search; - expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20210330PB40'); - expect(query.source).to.equal('pbjs10'); - expect(query.pbjs).to.equal('$prebid.version$'); - expect(query.adzoneid).to.equal('1'); - expect(query.format).to.equal('300x250|640x360|1x1'); - expect(query.jsonp).to.be.undefined; - expect(query.prebidBidIds).to.equal('84ab500420319d'); - expect(query.bidfloors).to.equal('0'); - - expect(query).to.have.property('secure'); - expect(query).to.have.property('uw'); - expect(query).to.have.property('uh'); - expect(query).to.have.property('dpr'); - expect(query).to.have.property('bt'); - expect(query).to.have.property('cookies'); - expect(query).to.have.property('tz'); - expect(query).to.have.property('dt'); - expect(query).to.have.property('iob'); - expect(query).to.have.property('rndid'); - expect(query).to.have.property('ref'); - expect(query).to.have.property('url'); + describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); }); + it('should send request with correct structure', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { + adzoneid: '19910113' + } + }]; + let request = spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}); - it('creates a valid adxcg request url bidVideo', function () { - let request = spec.buildRequests([bidVideo]); - expect(request).to.exist; - expect(request.method).to.equal('GET'); - let parsedRequestUrl = parseUrl(request.url); - expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); - expect(parsedRequestUrl.pathname).to.equal('/get/adi'); - - let query = parsedRequestUrl.search; - // general part - expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20210330PB40'); - expect(query.source).to.equal('pbjs10'); - expect(query.pbjs).to.equal('$prebid.version$'); - expect(query.adzoneid).to.equal('20'); - expect(query.format).to.equal('640x480'); - expect(query.jsonp).to.be.undefined; - expect(query.prebidBidIds).to.equal('84ab500420319d'); - expect(query.bidfloors).to.equal('0'); - - expect(query).to.have.property('secure'); - expect(query).to.have.property('uw'); - expect(query).to.have.property('uh'); - expect(query).to.have.property('dpr'); - expect(query).to.have.property('bt'); - expect(query).to.have.property('cookies'); - expect(query).to.have.property('tz'); - expect(query).to.have.property('dt'); - expect(query).to.have.property('iob'); - expect(query).to.have.property('rndid'); - expect(query).to.have.property('ref'); - expect(query).to.have.property('url'); - - // video specific part - expect(query['video.maxduration.0']).to.equal('30'); - expect(query['video.mimes.0']).to.equal('video/mp4'); - expect(query['video.context.0']).to.equal('instream'); + assert.equal(request.method, 'POST'); + assert.equal(request.url, 'https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); + assert.deepEqual(request.options, {contentType: 'application/json'}); + assert.ok(request.data); }); - it('creates a valid adxcg request url bidNative', function () { - let request = spec.buildRequests([bidNative]); - expect(request).to.exist; - expect(request.method).to.equal('GET'); - let parsedRequestUrl = parseUrl(request.url); - expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); - expect(parsedRequestUrl.pathname).to.equal('/get/adi'); - - let query = parsedRequestUrl.search; - expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20210330PB40'); - expect(query.source).to.equal('pbjs10'); - expect(query.pbjs).to.equal('$prebid.version$'); - expect(query.adzoneid).to.equal('2379'); - expect(query.format).to.equal('0x0'); - expect(query.jsonp).to.be.undefined; - expect(query.prebidBidIds).to.equal('84ab500420319d'); - expect(query.bidfloors).to.equal('0'); - - expect(query).to.have.property('secure'); - expect(query).to.have.property('uw'); - expect(query).to.have.property('uh'); - expect(query).to.have.property('dpr'); - expect(query).to.have.property('bt'); - expect(query).to.have.property('cookies'); - expect(query).to.have.property('tz'); - expect(query).to.have.property('dt'); - expect(query).to.have.property('iob'); - expect(query).to.have.property('rndid'); - expect(query).to.have.property('ref'); - expect(query).to.have.property('url'); - }); - }); + describe('user privacy', function () { + it('should send GDPR Consent data to exchange if gdprApplies', function () { + let validBidRequests = [{bidId: 'bidId', params: {test: 1}}]; + let bidderRequest = { + gdprConsent: {gdprApplies: true, consentString: 'consentDataString'}, + refererInfo: {referer: 'page'} + }; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.user.ext.consent, bidderRequest.gdprConsent.consentString); + assert.equal(request.regs.ext.gdpr, bidderRequest.gdprConsent.gdprApplies); + assert.equal(typeof request.regs.ext.gdpr, 'number'); + }); - describe('gdpr compliance', function () { - it('should send GDPR Consent data if gdprApplies', function () { - let request = spec.buildRequests([bidBanner], { - gdprConsent: { - gdprApplies: true, - consentString: 'consentDataString' - } + it('should send gdpr as number', function () { + let validBidRequests = [{bidId: 'bidId', params: {test: 1}}]; + let bidderRequest = { + gdprConsent: {gdprApplies: true, consentString: 'consentDataString'}, + refererInfo: {referer: 'page'} + }; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(typeof request.regs.ext.gdpr, 'number'); + assert.equal(request.regs.ext.gdpr, 1); }); - let parsedRequestUrl = parseUrl(request.url); - let query = parsedRequestUrl.search; - expect(query.gdpr).to.equal('1'); - expect(query.gdpr_consent).to.equal('consentDataString'); - }); + it('should send CCPA Consent data to exchange', function () { + let validBidRequests = [{bidId: 'bidId', params: {test: 1}}]; + let bidderRequest = {uspConsent: '1YA-', refererInfo: {referer: 'page'}}; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - it('should not send GDPR Consent data if gdprApplies is false or undefined', function () { - let request = spec.buildRequests([bidBanner], { - gdprConsent: { - gdprApplies: false, - consentString: 'consentDataString' - } + assert.equal(request.regs.ext.us_privacy, '1YA-'); + + bidderRequest = { + uspConsent: '1YA-', + gdprConsent: {gdprApplies: true, consentString: 'consentDataString'}, + refererInfo: {referer: 'page'} + }; + request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.regs.ext.us_privacy, '1YA-'); + assert.equal(request.user.ext.consent, 'consentDataString'); + assert.equal(request.regs.ext.gdpr, 1); }); - let parsedRequestUrl = parseUrl(request.url); - let query = parsedRequestUrl.search; - expect(query.gdpr).to.be.undefined; - expect(query.gdpr_consent).to.be.undefined; + it('should not send GDPR Consent data to adxcg if gdprApplies is undefined', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {siteId: 'siteId'} + }]; + let bidderRequest = { + gdprConsent: {gdprApplies: false, consentString: 'consentDataString'}, + refererInfo: {referer: 'page'} + }; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.user.ext.consent, 'consentDataString'); + assert.equal(request.regs.ext.gdpr, 0); + + bidderRequest = {gdprConsent: {consentString: 'consentDataString'}, refererInfo: {referer: 'page'}}; + request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.user, undefined); + assert.equal(request.regs, undefined); + }); + it('should send default GDPR Consent data to exchange', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {siteId: 'siteId'} + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); + + assert.equal(request.user, undefined); + assert.equal(request.regs, undefined); + }); }); - }); - describe('userid pubcid should be passed to querystring', function () { - let bidderRequests = {}; - let bid = deepClone([bidBanner]); - bid[0].userId = {pubcid: 'pubcidabcd'}; + it('should add test and is_debug to request, if test is set in parameters', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {test: 1} + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - it('should send pubcid if available', function () { - let request = spec.buildRequests(bid, bidderRequests); - let parsedRequestUrl = parseUrl(request.url); - let query = parsedRequestUrl.search; - expect(query.pubcid).to.equal('pubcidabcd'); + assert.ok(request.is_debug); + assert.equal(request.test, 1); }); - }); - describe('userid tdid should be passed to querystring', function () { - let bid = deepClone([bidBanner]); - let bidderRequests = {}; + it('should have default request structure', function () { + let keys = 'site,geo,device,source,ext,imp'.split(','); + let validBidRequests = [{ + bidId: 'bidId', + params: {siteId: 'siteId'} + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); + let data = Object.keys(request); + + assert.deepEqual(keys, data); + }); - bid[0].userId = {tdid: 'tdidabcd'}; + it('should set request keys correct values', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {siteId: 'siteId'}, + transactionId: 'transactionId' + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - it('should send pubcid if available', function () { - let request = spec.buildRequests(bid, bidderRequests); - let parsedRequestUrl = parseUrl(request.url); - let query = parsedRequestUrl.search; - expect(query.tdid).to.equal('tdidabcd'); + assert.equal(request.source.tid, validBidRequests[0].transactionId); + assert.equal(request.source.fd, 1); + }); + + it('should send info about device', function () { + config.setConfig({ + device: {w: 100, h: 100} + }); + let validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: '1000'} + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); + + assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.device.w, 100); + assert.equal(request.device.h, 100); }); - }); - describe('userid id5id should be passed to querystring', function () { - let bid = deepClone([bidBanner]); - let bidderRequests = {}; + it('should send app info', function () { + config.setConfig({ + app: {id: 'appid'}, + ortb2: {app: {name: 'appname'}} + }); + let validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: '1000'} + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); + + assert.equal(request.app.id, 'appid'); + assert.equal(request.app.name, 'appname'); + assert.equal(request.site, undefined); + }); - bid[0].userId = {id5id: {uid: 'id5idsample'}}; + it('should send info about the site', function () { + config.setConfig({ + site: { + id: '123123', + publisher: { + domain: 'publisher.domain.com' + } + }, + ortb2: { + site: { + publisher: { + id: 4441, + name: 'publisher\'s name' + } + } + } + }); + let validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: '1000'} + }]; + let refererInfo = {referer: 'page'}; + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo}).data); + + assert.deepEqual(request.site, { + domain: 'localhost', + id: '123123', + page: refererInfo.referer, + publisher: { + domain: 'publisher.domain.com', + id: 4441, + name: 'publisher\'s name' + } + }); + }); - it('should send pubcid if available', function () { - let request = spec.buildRequests(bid, bidderRequests); - let parsedRequestUrl = parseUrl(request.url); - let query = parsedRequestUrl.search; - expect(query.id5id).to.equal('id5idsample'); + it('should pass extended ids', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {}, + userIdAsEids: createEidsArray({ + tdid: 'TTD_ID_FROM_USER_ID_MODULE', + pubcid: 'pubCommonId_FROM_USER_ID_MODULE' + }) + }]; + + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); + assert.deepEqual(request.user.ext.eids, [ + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} + ]); }); - }); - describe('userid idl_env should be passed to querystring', function () { - let bid = deepClone([bidBanner]); - let bidderRequests = {}; + it('should send currency if defined', function () { + config.setConfig({currency: {adServerCurrency: 'EUR'}}); + let validBidRequests = [{params: {}}]; + let refererInfo = {referer: 'page'}; + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo}).data); + + assert.deepEqual(request.cur, ['EUR']); + }); - bid[0].userId = {idl_env: 'idl_envsample'}; + it('should pass supply chain object', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {}, + schain: { + validation: 'strict', + config: { + ver: '1.0' + } + } + }]; - it('should send pubcid if available', function () { - let request = spec.buildRequests(bid, bidderRequests); - let parsedRequestUrl = parseUrl(request.url); - let query = parsedRequestUrl.search; - expect(query.idl_env).to.equal('idl_envsample'); + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); + assert.deepEqual(request.source.ext.schain, { + validation: 'strict', + config: { + ver: '1.0' + } + }); }); - }); - describe('response handler', function () { - let BIDDER_REQUEST = { - bidder: 'adxcg', - params: { - adzoneid: '1' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [640, 360], - [1, 1] - ] + describe('bids', function () { + it('should add more than one bid to the request', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {siteId: 'siteId'} + }, { + bidId: 'bidId2', + params: {siteId: 'siteId'} + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); + + assert.equal(request.imp.length, 2); + }); + it('should add incrementing values of id', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: '1000'}, + mediaTypes: {video: {}} + }, { + bidId: 'bidId2', + params: {adzoneid: '1000'}, + mediaTypes: {video: {}} + }, { + bidId: 'bidId3', + params: {adzoneid: '1000'}, + mediaTypes: {video: {}} + }]; + let imps = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; + + for (let i = 0; i < 3; i++) { + assert.equal(imps[i].id, i + 1); } - }, - bidId: '84ab500420319d', - bidderRequestId: '7101db09af0db2', - auctionId: '1d1a030790a475' - }; + }); - let BANNER_RESPONSE = { - body: { - id: 'auctionid', - bidid: '84ab500420319d', - seatbid: [{ - bid: [ - { - impid: '84ab500420319d', - price: 0.45, - crid: '42', - adm: '', - w: 300, - h: 250, - adomain: ['adomain.com'], - cat: ['IAB1-4', 'IAB8-16', 'IAB25-5'], - ext: { - crType: 'banner', - advertiser_id: '777', - advertiser_name: 'advertiser', - agency_name: 'agency' - } + it('should add adzoneid', function () { + let validBidRequests = [{bidId: 'bidId', params: {adzoneid: 1000}, mediaTypes: {video: {}}}, + {bidId: 'bidId2', params: {adzoneid: 1001}, mediaTypes: {video: {}}}, + {bidId: 'bidId3', params: {adzoneid: 1002}, mediaTypes: {video: {}}}]; + let imps = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; + for (let i = 0; i < 3; i++) { + assert.equal(imps[i].tagid, validBidRequests[i].params.adzoneid); + } + }); + + describe('price floors', function () { + it('should not add if floors module not configured', function () { + const validBidRequests = [{bidId: 'bidId', params: {adzoneid: 1000}, mediaTypes: {video: {}}}]; + let imp = getRequestImps(validBidRequests)[0]; + + assert.equal(imp.bidfloor, undefined); + assert.equal(imp.bidfloorcur, undefined); + }); + + it('should not add if floor price not defined', function () { + const validBidRequests = [getBidWithFloor()]; + let imp = getRequestImps(validBidRequests)[0]; + + assert.equal(imp.bidfloor, undefined); + assert.equal(imp.bidfloorcur, 'USD'); + }); + + it('should request floor price in adserver currency', function () { + config.setConfig({currency: {adServerCurrency: 'DKK'}}); + const validBidRequests = [getBidWithFloor()]; + let imp = getRequestImps(validBidRequests)[0]; + + assert.equal(imp.bidfloor, undefined); + assert.equal(imp.bidfloorcur, 'DKK'); + }); + + it('should add correct floor values', function () { + const expectedFloors = [1, 1.3, 0.5]; + const validBidRequests = expectedFloors.map(getBidWithFloor); + let imps = getRequestImps(validBidRequests); + + expectedFloors.forEach((floor, index) => { + assert.equal(imps[index].bidfloor, floor); + assert.equal(imps[index].bidfloorcur, 'USD'); + }); + }); + + function getBidWithFloor(floor) { + return { + params: {adzoneid: 1}, + mediaTypes: {video: {}}, + getFloor: ({currency}) => { + return { + currency: currency, + floor + }; } - ] - }], - cur: 'USD' - }, - headers: {someheader: 'fakedata'} - }; + }; + } + }); - let BANNER_RESPONSE_WITHDEALID = { - body: { - id: 'auctionid', - bidid: '84ab500420319d', - seatbid: [{ - bid: [ - { - impid: '84ab500420319d', - price: 0.45, - crid: '42', - dealid: '7722', - adm: '', - w: 300, - h: 250, - adomain: ['adomain.com'], - ext: { - crType: 'banner' - } + describe('multiple media types', function () { + it('should use all configured media types for bidding', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: 1000}, + mediaTypes: { + banner: { + sizes: [[100, 100], [200, 300]] + }, + video: {} } - ] - }], - cur: 'USD' - } - }; + }, { + bidId: 'bidId1', + params: {adzoneid: 1000}, + mediaTypes: { + video: {}, + native: {} + } + }, { + bidId: 'bidId2', + params: {adzoneid: 1000}, + nativeParams: { + title: {required: true, len: 140} + }, + mediaTypes: { + banner: { + sizes: [[100, 100], [200, 300]] + }, + native: {}, + video: {} + } + }]; + let [first, second, third] = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; - let VIDEO_RESPONSE = { - body: { - id: 'auctionid', - bidid: '84ab500420319d', - seatbid: [{ - bid: [ - { - impid: '84ab500420319d', - price: 0.45, - crid: '42', - nurl: 'vastContentUrl', - adomain: ['adomain.com'], - w: 640, - h: 360, - ext: { - crType: 'video' + assert.ok(first.banner); + assert.ok(first.video); + assert.equal(first.native, undefined); + + assert.ok(second.video); + assert.equal(second.banner, undefined); + assert.equal(second.native, undefined); + + assert.ok(third.native); + assert.ok(third.video); + assert.ok(third.banner); + }); + }); + + describe('banner', function () { + it('should convert sizes to openrtb format', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: 1000}, + mediaTypes: { + banner: { + sizes: [[100, 100], [200, 300]] } } - ] - }], - cur: 'USD' - }, - headers: {someheader: 'fakedata'} - }; - - let NATIVE_RESPONSEob = { - assets: [ - { - id: 1, - required: 0, - title: { - text: 'titleContent' - } - }, - { - id: 2, - required: 0, - img: { - url: 'imageContent', - w: 600, - h: 600 - } - }, - { - id: 3, - required: 0, - data: { - label: 'DESC', - value: 'descriptionContent' - } - }, - { - id: 0, - required: 0, - data: { - label: 'SPONSORED', - value: 'sponsoredByContent' - } - }, - { - id: 5, - required: 0, - icon: { - url: 'iconContent', - w: 400, - h: 400 - } - } - ], - link: { - url: 'linkContent' - }, - imptrackers: ['impressionTracker1', 'impressionTracker2'] - } + }]; + let {banner} = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0]; + assert.deepEqual(banner, { + format: [{w: 100, h: 100}, {w: 200, h: 300}] + }); + }); + }); - let NATIVE_RESPONSE = { - body: { - id: 'auctionid', - bidid: '84ab500420319d', - seatbid: [{ - bid: [ - { - impid: '84ab500420319d', - price: 0.45, - crid: '42', - w: 0, - h: 0, - adm: JSON.stringify(NATIVE_RESPONSEob), - adomain: ['adomain.com'], - ext: { - crType: 'native' + describe('video', function () { + it('should pass video mediatype config', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: 1000}, + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'] } } - ] - }], - cur: 'USD' - }, - headers: {someheader: 'fakedata'} - }; + }]; + let {video} = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0]; + assert.deepEqual(video, { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'] + }); + }); + }); - it('handles regular responses', function () { - expect(BANNER_RESPONSE).to.exist; - expect(BANNER_RESPONSE.body).to.exist; - expect(BANNER_RESPONSE.body.id).to.exist; - expect(BANNER_RESPONSE.body.seatbid[0]).to.exist; - let result = spec.interpretResponse(BANNER_RESPONSE, BIDDER_REQUEST); - - expect(result).to.have.lengthOf(1); - - expect(result[0]).to.exist; - expect(result[0].width).to.equal(300); - expect(result[0].height).to.equal(250); - expect(result[0].creativeId).to.equal(42); - expect(result[0].cpm).to.be.within(0.45, 0.46); - expect(result[0].ad).to.equal(''); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].ttl).to.equal(300); - expect(result[0].dealId).to.not.exist; - expect(result[0].meta.advertiserDomains[0]).to.equal('adomain.com'); - expect(result[0].meta.advertiserId).to.be.eql('777'); - expect(result[0].meta.advertiserName).to.be.eql('advertiser'); - expect(result[0].meta.agencyName).to.be.eql('agency'); - expect(result[0].meta.advertiserDomains).to.be.eql(['adomain.com']); - expect(result[0].meta.secondaryCatIds).to.be.eql(['IAB1-4', 'IAB8-16', 'IAB25-5']); + describe('native', function () { + describe('assets', function () { + it('should set correct asset id', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: 1000}, + nativeParams: { + title: {required: true, len: 140}, + image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, + body: {len: 140} + } + }]; + let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; + let assets = JSON.parse(nativeRequest).assets; + + assert.equal(assets[0].id, 0); + assert.equal(assets[1].id, 3); + assert.equal(assets[2].id, 4); + }); + it('should add required key if it is necessary', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: 1000}, + nativeParams: { + title: {required: true, len: 140}, + image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, + body: {len: 140}, + sponsoredBy: {required: true, len: 140} + } + }]; + + let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; + let assets = JSON.parse(nativeRequest).assets; + + assert.equal(assets[0].required, 1); + assert.ok(!assets[1].required); + assert.ok(!assets[2].required); + assert.equal(assets[3].required, 1); + }); + + it('should map img and data assets', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: 1000}, + nativeParams: { + title: {required: true, len: 140}, + image: {required: true, sizes: [150, 50]}, + icon: {required: false, sizes: [50, 50]}, + body: {required: false, len: 140}, + sponsoredBy: {required: true}, + cta: {required: false}, + clickUrl: {required: false} + } + }]; + + let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; + let assets = JSON.parse(nativeRequest).assets; + assert.ok(assets[0].title); + assert.equal(assets[0].title.len, 140); + assert.deepEqual(assets[1].img, {type: 3, w: 150, h: 50}); + assert.deepEqual(assets[2].img, {type: 1, w: 50, h: 50}); + assert.deepEqual(assets[3].data, {type: 2, len: 140}); + assert.deepEqual(assets[4].data, {type: 1}); + assert.deepEqual(assets[5].data, {type: 12}); + assert.ok(!assets[6]); + }); + + describe('icon/image sizing', function () { + it('should flatten sizes and utilise first pair', function () { + const validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: 1000}, + nativeParams: { + image: { + sizes: [[200, 300], [100, 200]] + }, + } + }]; + + let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; + let assets = JSON.parse(nativeRequest).assets; + assert.ok(assets[0].img); + assert.equal(assets[0].img.w, 200); + assert.equal(assets[0].img.h, 300); + }); + }); + + it('should utilise aspect_ratios', function () { + const validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: 1000}, + nativeParams: { + image: { + aspect_ratios: [{ + min_width: 100, + ratio_height: 3, + ratio_width: 1 + }] + }, + icon: { + aspect_ratios: [{ + min_width: 10, + ratio_height: 5, + ratio_width: 2 + }] + } + } + }]; + + let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; + let assets = JSON.parse(nativeRequest).assets; + assert.ok(assets[0].img); + assert.equal(assets[0].img.wmin, 100); + assert.equal(assets[0].img.hmin, 300); + + assert.ok(assets[1].img); + assert.equal(assets[1].img.wmin, 10); + assert.equal(assets[1].img.hmin, 25); + }); + + it('should not throw error if aspect_ratios config is not defined', function () { + const validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: 1000}, + nativeParams: { + image: { + aspect_ratios: [] + }, + icon: { + aspect_ratios: [] + } + } + }]; + + assert.doesNotThrow(() => spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}})); + }); + }); + + it('should expect any dimensions if min_width not passed', function () { + const validBidRequests = [{ + bidId: 'bidId', + params: {adzoneid: 1000}, + nativeParams: { + image: { + aspect_ratios: [{ + ratio_height: 3, + ratio_width: 1 + }] + } + } + }]; + + let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; + let assets = JSON.parse(nativeRequest).assets; + assert.ok(assets[0].img); + assert.equal(assets[0].img.wmin, 0); + assert.equal(assets[0].img.hmin, 0); + assert.ok(!assets[1]); + }); + }); }); - it('handles regular responses with dealid', function () { - let result = spec.interpretResponse(BANNER_RESPONSE_WITHDEALID); + function getRequestImps(validBidRequests) { + return JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; + } + }); - expect(result).to.have.lengthOf(1); + describe('interpretResponse', function () { + it('should return if no body in response', function () { + let serverResponse = {}; + let bidRequest = {}; - expect(result[0].width).to.equal(300); - expect(result[0].height).to.equal(250); - expect(result[0].creativeId).to.equal(42); - // expect(result[0].cpm).to.equal(0.45); - expect(result[0].cpm).to.be.within(0.45, 0.46); - expect(result[0].ad).to.equal(''); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].ttl).to.equal(300); + assert.ok(!spec.interpretResponse(serverResponse, bidRequest)); }); + it('should return more than one bids', function () { + let serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: '1', + native: {ver: '1.1', link: {url: 'link'}, assets: [{id: 1, title: {text: 'Asset title text'}}]} + }] + }, { + bid: [{ + impid: '2', + native: {ver: '1.1', link: {url: 'link'}, assets: [{id: 1, data: {value: 'Asset title text'}}]} + }] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: {adzoneid: 1000}, + nativeParams: { + title: {required: true, len: 140}, + image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, + body: {len: 140} + } + }, + { + bidId: 'bidId2', + params: {adzoneid: 1000}, + nativeParams: { + title: {required: true, len: 140}, + image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, + body: {len: 140} + } + } + ] + }; - it('handles video responses', function () { - let result = spec.interpretResponse(VIDEO_RESPONSE); - expect(result).to.have.lengthOf(1); - - expect(result[0].width).to.equal(640); - expect(result[0].height).to.equal(360); - expect(result[0].mediaType).to.equal('video'); - expect(result[0].creativeId).to.equal(42); - expect(result[0].cpm).to.equal(0.45); - expect(result[0].vastUrl).to.equal('vastContentUrl'); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].ttl).to.equal(300); + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(spec.interpretResponse(serverResponse, bidRequest).length, 2); }); - it('handles native responses', function () { - let result = spec.interpretResponse(NATIVE_RESPONSE); - - expect(result[0].width).to.equal(0); - expect(result[0].height).to.equal(0); - - expect(result[0].creativeId).to.equal(42); - expect(result[0].cpm).to.equal(0.45); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].ttl).to.equal(300); + it('should set correct values to bid', function () { + let nativeExample1 = { + assets: [], + link: {url: 'link'}, + imptrackers: ['imptrackers url1', 'imptrackers url2'] + } - expect(result[0].mediaType).to.equal('native'); + let serverResponse = { + body: { + id: null, + bidid: null, + seatbid: [{ + bid: [ + { + impid: '1', + price: 93.1231, + crid: '12312312', + adm: JSON.stringify(nativeExample1), + dealid: 'deal-id', + adomain: ['demo.com'], + ext: { + crType: 'native', + advertiser_id: 'adv1', + advertiser_name: 'advname', + agency_name: 'agname', + mediaType: 'native' + } + } + ] + }], + cur: 'EUR' + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: {adzoneid: 1000}, + nativeParams: { + title: {required: true, len: 140}, + image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, + body: {len: 140} + } + } + ] + }; - expect(result[0].native.clickUrl).to.equal('linkContent'); - expect(result[0].native.impressionTrackers).to.deep.equal([ - 'impressionTracker1', - 'impressionTracker2' - ]); - expect(result[0].native.title).to.equal('titleContent'); + const bids = spec.interpretResponse(serverResponse, bidRequest); + const bid = serverResponse.body.seatbid[0].bid[0]; + assert.deepEqual(bids[0].requestId, bidRequest.bids[0].bidId); + assert.deepEqual(bids[0].cpm, bid.price); + assert.deepEqual(bids[0].creativeId, bid.crid); + assert.deepEqual(bids[0].ttl, 300); + assert.deepEqual(bids[0].netRevenue, false); + assert.deepEqual(bids[0].currency, serverResponse.body.cur); + assert.deepEqual(bids[0].mediaType, 'native'); + assert.deepEqual(bids[0].meta.mediaType, 'native'); + assert.deepEqual(bids[0].meta.advertiserDomains, ['demo.com']); + + assert.deepEqual(bids[0].meta.advertiserName, 'advname'); + assert.deepEqual(bids[0].meta.agencyName, 'agname'); + + assert.deepEqual(bids[0].dealId, 'deal-id'); + }); - expect(result[0].native.image.url).to.equal('imageContent'); - expect(result[0].native.image.height).to.equal(600); - expect(result[0].native.image.width).to.equal(600); + it('should return empty when there is no bids in response', function () { + const serverResponse = { + body: { + id: null, + bidid: null, + seatbid: [{bid: []}], + cur: 'EUR' + } + }; + let bidRequest = { + data: {}, + bids: [{bidId: 'bidId1'}] + }; + const result = spec.interpretResponse(serverResponse, bidRequest)[0]; + assert.ok(!result); + }); - expect(result[0].native.icon.url).to.equal('iconContent'); - expect(result[0].native.icon.height).to.equal(400); - expect(result[0].native.icon.width).to.equal(400); + describe('banner', function () { + it('should set ad content on response', function () { + let serverResponse = { + body: { + seatbid: [{ + bid: [{impid: '1', adm: '', ext: {crType: 'banner'}}] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: {adzoneid: 1000} + } + ] + }; - expect(result[0].native.body).to.equal('descriptionContent'); - expect(result[0].native.sponsoredBy).to.equal('sponsoredByContent'); + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(bids.length, 1); + assert.equal(bids[0].ad, ''); + assert.equal(bids[0].mediaType, 'banner'); + assert.equal(bids[0].meta.mediaType, 'banner'); + }); }); - it('handles nobid responses', function () { - let response = []; - let bidderRequest = BIDDER_REQUEST; + describe('video', function () { + it('should set vastXml on response', function () { + let serverResponse = { + body: { + seatbid: [{ + bid: [{impid: '1', adm: '', ext: {crType: 'video'}}] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: {adzoneid: 1000} + } + ] + }; - let result = spec.interpretResponse(response, bidderRequest); - expect(result.length).to.equal(0); + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(bids.length, 1); + assert.equal(bids[0].vastXml, ''); + assert.equal(bids[0].mediaType, 'video'); + assert.equal(bids[0].meta.mediaType, 'video'); + }); }); }); describe('getUserSyncs', function () { - let syncoptionsIframe = { - iframeEnabled: 'true' - }; + const usersyncUrl = 'https://usersync-url.com'; + beforeEach(() => { + config.setConfig( + { + adxcg: { + usersyncUrl: usersyncUrl, + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should return user sync if pixel enabled with adxcg config', function () { + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.deep.equal([{type: 'image', url: usersyncUrl}]) + }) + + it('should not return user sync if pixel disabled', function () { + const ret = spec.getUserSyncs({pixelEnabled: false}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should not return user sync if url is not set', function () { + config.resetConfig() + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should pass GDPR consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=` + }]); + }); + + it('should pass US consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?us_privacy=1NYN` + }]); + }); - it('should return iframe sync option', function () { - expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.equal('iframe'); - expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal( - 'https://cdn.adxcg.net/pb-sync.html' - ); + it('should pass GDPR and US consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` + }]); }); }); - describe('on bidWon', function () { - beforeEach(function () { + describe('onBidWon', function() { + beforeEach(function() { sinon.stub(utils, 'triggerPixel'); }); - afterEach(function () { + afterEach(function() { utils.triggerPixel.restore(); }); - it('should replace burl for banner', function () { - const burl = 'burl=${' + 'AUCTION_PRICE}'; + + it('Should trigger pixel if bid nurl', function() { const bid = { - 'bidderCode': 'adxcg', - 'width': 0, - 'height': 0, - 'statusMessage': 'Bid available', - 'adId': '3d0b6ff1dda89', - 'requestId': '2a423489e058a1', - 'mediaType': 'banner', - 'source': 'client', - 'ad': burl, - 'cpm': 0.66, - 'creativeId': '353538_591471', - 'currency': 'USD', - 'dealId': '', - 'netRevenue': true, - 'ttl': 300, - // 'nurl': nurl, - 'burl': burl, - 'isBurl': true, - 'auctionId': 'a92bffce-14d2-4f8f-a78a-7b9b5e4d28fa', - 'responseTimestamp': 1556867386065, - 'requestTimestamp': 1556867385916, - 'bidder': 'adxcg', - 'adUnitCode': 'div-gpt-ad-1555415275793-0', - 'timeToRespond': 149, - 'pbLg': '0.50', - 'pbMg': '0.60', - 'pbHg': '0.66', - 'pbAg': '0.65', - 'pbDg': '0.66', - 'pbCg': '', - 'size': '0x0', - 'adserverTargeting': { - 'hb_bidder': 'mgid', - 'hb_adid': '3d0b6ff1dda89', - 'hb_pb': '0.66', - 'hb_size': '0x0', - 'hb_source': 'client', - 'hb_format': 'banner', - 'hb_banner_title': 'TITLE', - 'hb_banner_image': 'hb_banner_image:3d0b6ff1dda89', - 'hb_banner_icon': 'IconURL', - 'hb_banner_linkurl': 'hb_banner_linkurl:3d0b6ff1dda89' - }, - 'status': 'targetingSet', - 'params': [{'adzoneid': '20'}] - }; + nurl: 'http://example.com/win/${AUCTION_PRICE}', + cpm: 2.1, + originalCpm: 1.1, + } spec.onBidWon(bid); - expect(bid.burl).to.deep.equal(burl); - }); - }); + expect(utils.triggerPixel.callCount).to.equal(1) + }) + }) }); diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index c432ad1b32d..1c5a1c7ac36 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -95,6 +95,40 @@ describe('Adyoulike Adapter', function () { } ]; + const bidRequestWithMultipleMediatype = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' + }, + 'sizes': '300x250', + 'mediaTypes': { + 'banner': { + 'sizes': ['640x480'] + }, + 'video': { + 'playerSize': [640, 480], + 'context': 'outstream' + }, + 'native': { + 'image': { + 'required': true, + }, + 'title': { + 'required': true, + 'len': 80 + }, + 'cta': { + 'required': false + }, + } + }, + 'transactionId': 'bid_id_0_transaction_id' + } + ]; + const bidRequestWithNativeImageType = [ { 'bidId': 'bid_id_0', @@ -158,6 +192,17 @@ describe('Adyoulike Adapter', function () { } }; + const sentBidVideo = { + 'bid_id_0': { + 'PlacementID': 'e622af275681965d3095808561a1e510', + 'TransactionID': 'e8355240-d976-4cd5-a493-640656fe08e8', + 'AvailableSizes': '', + 'Video': { + playerSize: [640, 480] + } + } + }; + const sentNativeImageType = { 'additional': { 'sent': [ @@ -290,7 +335,19 @@ describe('Adyoulike Adapter', function () { 'Placement': 'placement_0' } ]; - const admSample = "\u003cscript id=\"ayl-prebid-a11a121205932e75e622af275681965d\"\u003e\n(function(){\n\twindow.isPrebid = true\n\tvar prebidResults = /*PREBID*/{\"OnEvents\":{\"CLICK\":[{\"Kind\":\"PIXEL_URL\",\"Url\":\"https://testPixelCLICK.com/fake\"}],\"IMPRESSION\":[{\"Kind\":\"PIXEL_URL\",\"Url\":\"https://testPixelIMP.com/fake\"},{\"Kind\":\"JAVASCRIPT_URL\",\"Url\":\"https://testJsIMP.com/fake.js\"}]},\"Disabled\":false,\"Attempt\":\"a11a121205932e75e622af275681965d\",\"ApiPrefix\":\"https://fo-api.omnitagjs.com/fo-api\",\"TrackingPrefix\":\"https://tracking.omnitagjs.com/tracking\",\"DynamicPrefix\":\"https://tag-dyn.omnitagjs.com/fo-dyn\",\"StaticPrefix\":\"https://fo-static.omnitagjs.com/fo-static\",\"BlobPrefix\":\"https://fo-api.omnitagjs.com/fo-api/blobs\",\"SspPrefix\":\"https://fo-ssp.omnitagjs.com/fo-ssp\",\"VisitorPrefix\":\"https://visitor.omnitagjs.com/visitor\",\"Trusted\":true,\"Placement\":\"e622af275681965d3095808561a1e510\",\"PlacementAccess\":\"ALL\",\"Site\":\"6e2df7a92203c3c7a25561ed63f25a27\",\"Lang\":\"EN\",\"SiteLogo\":null,\"HasSponsorImage\":false,\"ResizeIframe\":true,\"IntegrationConfig\":{\"Kind\":\"WIDGET\",\"Widget\":{\"ExtraStyleSheet\":\"\",\"Placeholders\":{\"Body\":{\"Color\":{\"R\":77,\"G\":21,\"B\":82,\"A\":100},\"BackgroundColor\":{\"R\":255,\"G\":255,\"B\":255,\"A\":100},\"FontFamily\":\"Lato\",\"Width\":\"100%\",\"Align\":\"\",\"BoxShadow\":true},\"CallToAction\":{\"Color\":{\"R\":26,\"G\":157,\"B\":212,\"A\":100}},\"Description\":{\"Length\":130},\"Image\":{\"Width\":600,\"Height\":600,\"Lowres\":false,\"Raw\":false},\"Size\":{\"Height\":\"250px\",\"Width\":\"300px\"},\"Sponsor\":{\"Color\":{\"R\":35,\"G\":35,\"B\":35,\"A\":100},\"Label\":true,\"WithoutLogo\":false},\"Title\":{\"Color\":{\"R\":219,\"G\":181,\"B\":255,\"A\":100}}},\"Selector\":{\"Kind\":\"CSS\",\"Css\":\"#ayl-prebid-a11a121205932e75e622af275681965d\"},\"Insertion\":\"AFTER\",\"ClickFormat\":true,\"Creative20\":true,\"WidgetKind\":\"CREATIVE_TEMPLATE_4\"}},\"Legal\":\"Sponsored\",\"ForcedCampaign\":\"f1c80d4bb5643c222ae8de75e9b2f991\",\"ForcedTrack\":\"\",\"ForcedCreative\":\"\",\"ForcedSource\":\"\",\"DisplayMode\":\"DEFAULT\",\"Campaign\":\"f1c80d4bb5643c222ae8de75e9b2f991\",\"CampaignAccess\":\"ALL\",\"CampaignKind\":\"AD_TRAFFIC\",\"DataSource\":\"LOCAL\",\"DataSourceUrl\":\"\",\"DataSourceOnEventsIsolated\":false,\"DataSourceWithoutCookie\":false,\"Content\":{\"Preview\":{\"Thumbnail\":{\"Image\":{\"Kind\":\"EXTERNAL\",\"Data\":{\"External\":{\"Url\":\"https://tag-dyn.omnitagjs.com/fo-dyn/native/preview/image?key=fd4362d35bb174d6f1c80d4bb5643c22\\u0026kind=INTERNAL\\u0026ztop=0.000000\\u0026zleft=0.000000\\u0026zwidth=0.333333\\u0026zheight=1.000000\\u0026width=[width]\\u0026height=[height]\"}},\"ZoneTop\":0,\"ZoneLeft\":0,\"ZoneWidth\":1,\"ZoneHeight\":1,\"Smart\":false,\"NoTransform\":false,\"Quality\":\"NORMAL\"}},\"Text\":{\"CALLTOACTION\":\"Click here to learn more\",\"DESCRIPTION\":\"Considérant l'extrémité conjoncturelle, il serait bon d'anticiper toutes les voies de bon sens.\",\"SPONSOR\":\"Tested by\",\"TITLE\":\"Adserver Traffic Redirect Internal\"},\"Sponsor\":{\"Name\":\"QA Team\"},\"Credit\":{\"Logo\":{\"Resource\":{\"Kind\":\"EXTERNAL\",\"Data\":{\"External\":{\"Url\":\"https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.png\"}},\"ZoneTop\":0,\"ZoneLeft\":0,\"ZoneWidth\":1,\"ZoneHeight\":1,\"Smart\":false,\"NoTransform\":false,\"Quality\":\"NORMAL\"}},\"Url\":\"https://blobs.omnitagjs.com/adchoice/\"}},\"Landing\":{\"Url\":\"https://www.w3.org/People/mimasa/test/xhtml/entities/entities-11.xhtml#lat1\",\"LegacyTracking\":false},\"ViewButtons\":{\"Close\":{\"Skip\":6000}},\"InternalContentFields\":{\"AnimatedImage\":false}},\"AdDomain\":\"adyoulike.com\",\"Opener\":\"REDIRECT\",\"PerformUITriggers\":[\"CLICK\"],\"RedirectionTarget\":\"TAB\"}/*PREBID*/;\n\tvar insertAds = function insertAds() {\insertAds();\n\t}\n})();\n\u003c/script\u003e"; + + const testMetaObject = { + 'networkId': 123, + 'advertiserId': '3', + 'advertiserName': 'foobar', + 'advertiserDomains': ['foobar.com'], + 'brandId': '345', + 'brandName': 'Foo', + 'primaryCatId': '34', + 'secondaryCatIds': ['IAB-222', 'IAB-333'], + 'mediaType': 'banner' + }; + const admSample = "\u003cscript id=\"ayl-prebid-a11a121205932e75e622af275681965d\"\u003e\n(function(){\n\twindow.isPrebid = true\n\tvar prebidResults = /*PREBID*/{\"OnEvents\": {\"CLICK\": [{\"Kind\": \"PIXEL_URL\",\"Url\": \"https://testPixelCLICK.com/fake\"}],\"IMPRESSION\": [{\"Kind\": \"PIXEL_URL\",\"Url\": \"https://testPixelIMP.com/fake\"}, {\"Kind\": \"JAVASCRIPT_URL\",\"Url\": \"https://testJsIMP.com/fake.js\"}]},\"Disabled\": false,\"Attempt\": \"a11a121205932e75e622af275681965d\",\"ApiPrefix\": \"https://fo-api.omnitagjs.com/fo-api\",\"TrackingPrefix\": \"https://tracking.omnitagjs.com/tracking\",\"DynamicPrefix\": \"https://tag-dyn.omnitagjs.com/fo-dyn\",\"StaticPrefix\": \"https://fo-static.omnitagjs.com/fo-static\",\"BlobPrefix\": \"https://fo-api.omnitagjs.com/fo-api/blobs\",\"SspPrefix\": \"https://fo-ssp.omnitagjs.com/fo-ssp\",\"VisitorPrefix\": \"https://visitor.omnitagjs.com/visitor\",\"Trusted\": true,\"Placement\": \"e622af275681965d3095808561a1e510\",\"PlacementAccess\": \"ALL\",\"Site\": \"6e2df7a92203c3c7a25561ed63f25a27\",\"Lang\": \"EN\",\"SiteLogo\": null,\"HasSponsorImage\": true,\"ResizeIframe\": true,\"IntegrationConfig\": {\"Kind\": \"WIDGET\",\"Widget\": {\"ExtraStyleSheet\": \"\",\"Placeholders\": {\"Body\": {\"Color\": {\"R\": 77,\"G\": 21,\"B\": 82,\"A\": 100},\"BackgroundColor\": {\"R\": 255,\"G\": 255,\"B\": 255,\"A\": 100},\"FontFamily\": \"Lato\",\"Width\": \"100%\",\"Align\": \"\",\"BoxShadow\": true},\"CallToAction\": {\"Color\": {\"R\": 26,\"G\": 157,\"B\": 212,\"A\": 100}},\"Description\": {\"Length\": 130},\"Image\": {\"Width\": 600,\"Height\": 600,\"Lowres\": false,\"Raw\": false},\"Size\": {\"Height\": \"250px\",\"Width\": \"300px\"},\"Title\": {\"Color\": {\"R\": 219,\"G\": 181,\"B\": 255,\"A\": 100}}},\"Selector\": {\"Kind\": \"CSS\",\"Css\": \"#ayl-prebid-a11a121205932e75e622af275681965d\"},\"Insertion\": \"AFTER\",\"ClickFormat\": true,\"Creative20\": true,\"WidgetKind\": \"CREATIVE_TEMPLATE_4\"}},\"Legal\": \"Sponsored\",\"ForcedCampaign\": \"f1c80d4bb5643c222ae8de75e9b2f991\",\"ForcedTrack\": \"\",\"ForcedCreative\": \"\",\"ForcedSource\": \"\",\"DisplayMode\": \"DEFAULT\",\"Campaign\": \"f1c80d4bb5643c222ae8de75e9b2f991\",\"CampaignAccess\": \"ALL\",\"CampaignKind\": \"AD_TRAFFIC\",\"DataSource\": \"LOCAL\",\"DataSourceUrl\": \"\",\"DataSourceOnEventsIsolated\": false,\"DataSourceWithoutCookie\": false,\"Content\": {\"Preview\": {\"Thumbnail\": {\"Image\": {\"Kind\": \"EXTERNAL\",\"Data\": {\"External\": {\"Url\": \"https://tag-dyn.omnitagjs.com/fo-dyn/native/preview/image?key=fd4362d35bb174d6f1c80d4bb5643c22\\u0026kind=INTERNAL\\u0026ztop=0.000000\\u0026zleft=0.000000\\u0026zwidth=0.333333\\u0026zheight=1.000000\\u0026width=[width]\\u0026height=[height]\"}},\"ZoneTop\": 0,\"ZoneLeft\": 0,\"ZoneWidth\": 1,\"ZoneHeight\": 1,\"Smart\": false,\"NoTransform\": false,\"Quality\": \"NORMAL\"}},\"Text\": {\"CALLTOACTION\": \"Click here to learn more\",\"DESCRIPTION\": \"Considérant l'extrémité conjoncturelle, il serait bon d'anticiper toutes les voies de bon sens.\",\"SPONSOR\": \"Tested by\",\"TITLE\": \"Adserver Traffic Redirect Internal\"},\"Sponsor\": {\"Name\": \"QA Team\",\"Logo\": {\"Resource\": {\"Kind\": \"EXTERNAL\",\"Data\": {\"External\": {\"Url\": \"https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.svg\"}},\"ZoneTop\": 0,\"ZoneLeft\": 0,\"ZoneWidth\": 1,\"ZoneHeight\": 1,\"Smart\": false,\"NoTransform\": false,\"Quality\": \"NORMAL\"}}},\"Credit\": {\"Logo\": {\"Resource\": {\"Kind\": \"EXTERNAL\",\"Data\": {\"External\": {\"Url\": \"https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.png\"}},\"ZoneTop\": 0,\"ZoneLeft\": 0,\"ZoneWidth\": 1,\"ZoneHeight\": 1,\"Smart\": false,\"NoTransform\": false,\"Quality\": \"NORMAL\"}},\"Url\": \"https://blobs.omnitagjs.com/adchoice/\"}},\"Landing\": {\"Url\": \"https://www.w3.org/People/mimasa/test/xhtml/entities/entities-11.xhtml#lat1\",\"LegacyTracking\": false},\"ViewButtons\": {\"Close\": {\"Skip\": 6000}},\"InternalContentFields\": {\"AnimatedImage\": false}},\"AdDomain\": \"adyoulike.com\",\"Opener\": \"REDIRECT\",\"PerformUITriggers\": [\"CLICK\"],\"RedirectionTarget\": \"TAB\"}/*PREBID*/;\n\tvar insertAds = function insertAds() {\insertAds();\n\t}\n})();\n\u003c/script\u003e"; const responseWithSinglePlacement = [ { 'BidID': 'bid_id_0', @@ -298,8 +355,110 @@ describe('Adyoulike Adapter', function () { 'Ad': admSample, 'Price': 0.5, 'Height': 600, + 'Meta': testMetaObject } ]; + + const responseWithSingleNative = [{ + 'BidID': 'bid_id_0', + 'Placement': 'placement_0', + 'Native': { + 'body': 'Considérant l\'extrémité conjoncturelle, il serait bon d\'anticiper toutes les voies de bon sens.', + 'cta': 'Click here to learn more', + 'clickUrl': 'https://tracking.omnitagjs.com/tracking/ar?event_kind=CLICK&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991&url=https%3A%2F%2Fwww.w3.org%2FPeople%2Fmimasa%2Ftest%2Fxhtml%2Fentities%2Fentities-11.xhtml%23lat1', + 'image': { + 'height': 600, + 'url': 'https://blobs.omnitagjs.com/blobs/f1/f1c80d4bb5643c22/fd4362d35bb174d6f1c80d4bb5643c22', + 'width': 300 + }, + 'icon': { + 'height': 50, + 'url': 'https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.svg', + 'width': 50, + }, + 'privacyIcon': 'https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.png', + 'privacyLink': 'https://blobs.omnitagjs.com/adchoice/', + 'sponsoredBy': 'QA Team', + 'title': 'Adserver Traffic Redirect Internal', + 'impressionTrackers': [ + 'https://testPixelIMP.com/fake', + 'https://tracking.omnitagjs.com/tracking/pixel?event_kind=IMPRESSION&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991', + 'https://tracking.omnitagjs.com/tracking/pixel?event_kind=INSERTION&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991', + ], + 'javascriptTrackers': [ + 'https://testJsIMP.com/fake.js' + ], + 'clickTrackers': [ + 'https://testPixelCLICK.com/fake' + ] + }, + 'Price': 0.5, + 'Height': 600, + }]; + + const nativeResult = [{ + cpm: 0.5, + creativeId: undefined, + currency: 'USD', + netRevenue: true, + requestId: 'bid_id_0', + ttl: 3600, + mediaType: 'native', + native: { + body: 'Considérant l\'extrémité conjoncturelle, il serait bon d\'anticiper toutes les voies de bon sens.', + clickTrackers: [ + 'https://testPixelCLICK.com/fake' + ], + clickUrl: 'https://tracking.omnitagjs.com/tracking/ar?event_kind=CLICK&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991&url=https%3A%2F%2Fwww.w3.org%2FPeople%2Fmimasa%2Ftest%2Fxhtml%2Fentities%2Fentities-11.xhtml%23lat1', + cta: 'Click here to learn more', + image: { + height: 600, + url: 'https://blobs.omnitagjs.com/blobs/f1/f1c80d4bb5643c22/fd4362d35bb174d6f1c80d4bb5643c22', + width: 300, + }, + icon: { + height: 50, + url: 'https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.svg', + width: 50, + }, + impressionTrackers: [ + 'https://testPixelIMP.com/fake', + 'https://tracking.omnitagjs.com/tracking/pixel?event_kind=IMPRESSION&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991', + 'https://tracking.omnitagjs.com/tracking/pixel?event_kind=INSERTION&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991' + ], + javascriptTrackers: [ + 'https://testJsIMP.com/fake.js' + ], + privacyIcon: 'https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.png', + privacyLink: 'https://blobs.omnitagjs.com/adchoice/', + sponsoredBy: 'QA Team', + title: 'Adserver Traffic Redirect Internal', + }, + meta: testMetaObject + }]; + + const responseWithSingleVideo = [{ + 'BidID': 'bid_id_0', + 'Placement': 'placement_0', + 'Vast': 'PFZBU1Q+RW1wdHkgc2FtcGxlPC92YXN0Pg==', + 'Price': 0.5, + 'Height': 600, + }]; + + const videoResult = [{ + cpm: 0.5, + creativeId: undefined, + currency: 'USD', + netRevenue: true, + requestId: 'bid_id_0', + ttl: 3600, + mediaType: 'video', + meta: { + advertiserDomains: [] + }, + vastXml: 'Empty sample' + }]; + const responseWithMultiplePlacements = [ { 'BidID': 'bid_id_0', @@ -340,6 +499,17 @@ describe('Adyoulike Adapter', function () { 'transactionId': 'bid_id_1_transaction_id' }; + let bidWSize = { + 'bidId': 'bid_id_1', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-1', + 'params': { + 'placement': 'placement_1', + 'size': [250, 300], + }, + 'transactionId': 'bid_id_1_transaction_id' + }; + let nativeBid = { 'bidId': 'bid_id_1', 'bidder': 'adyoulike', @@ -359,6 +529,10 @@ describe('Adyoulike Adapter', function () { expect(!!spec.isBidRequestValid(bid)).to.equal(true); }); + it('should return true when required params found with size in bid params', function () { + expect(!!spec.isBidRequestValid(bidWSize)).to.equal(true); + }); + it('should return true when required params found for native ad', function () { expect(!!spec.isBidRequestValid(nativeBid)).to.equal(true); }); @@ -460,6 +634,32 @@ describe('Adyoulike Adapter', function () { expect(payload.gdprConsent.consentRequired).to.be.null; }); + it('should add userid eids information to the request', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'userId': { + pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', + unsuported: '666' + } + }; + + bidderRequest.bids = bidRequestWithSinglePlacement; + + const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.userId).to.exist; + expect(payload.userId).to.deep.equal([{ + 'source': 'pubcid.org', + 'uids': [{ + 'atype': 1, + 'id': '01EAJWWNEPN3CYMM5N8M5VXY22' + }] + }]); + }); + it('sends bid request to endpoint with single placement', function () { const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); const payload = JSON.parse(request.data); @@ -491,6 +691,21 @@ describe('Adyoulike Adapter', function () { expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); + it('sends bid request to endpoint with single placement multiple mediatype', function () { + canonicalQuery.restore(); + const request = spec.buildRequests(bidRequestWithMultipleMediatype, bidderRequest); + const payload = JSON.parse(request.data); + + expect(request.url).to.contain(getEndpoint()); + expect(request.method).to.equal('POST'); + + expect(request.url).to.not.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + expect(payload.Version).to.equal('1.0'); + expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); + expect(payload.PageRefreshed).to.equal(false); + expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); + }); + it('sends bid request to endpoint with multiple placements', function () { const request = spec.buildRequests(bidRequestMultiPlacements, bidderRequest); const payload = JSON.parse(request.data); @@ -549,6 +764,7 @@ describe('Adyoulike Adapter', function () { expect(result[0].ad).to.equal(admSample); expect(result[0].width).to.equal(300); expect(result[0].height).to.equal(600); + expect(result[0].meta).to.deep.equal(testMetaObject); }); it('receive reponse with multiple placement', function () { @@ -568,47 +784,40 @@ describe('Adyoulike Adapter', function () { expect(result[1].height).to.equal(250); }); - it('receive reponse with Native ad', function () { + it('receive reponse with Native from ad markup', function () { serverResponse.body = responseWithSinglePlacement; let result = spec.interpretResponse(serverResponse, {data: '{"Bids":' + JSON.stringify(sentBidNative) + '}'}); expect(result.length).to.equal(1); - expect(result).to.deep.equal([{ - cpm: 0.5, - creativeId: undefined, - currency: 'USD', - netRevenue: true, - requestId: 'bid_id_0', - ttl: 3600, - mediaType: 'native', - native: { - body: 'Considérant l\'extrémité conjoncturelle, il serait bon d\'anticiper toutes les voies de bon sens.', - clickTrackers: [ - 'https://testPixelCLICK.com/fake' - ], - clickUrl: 'https://tracking.omnitagjs.com/tracking/ar?event_kind=CLICK&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991&url=https%3A%2F%2Fwww.w3.org%2FPeople%2Fmimasa%2Ftest%2Fxhtml%2Fentities%2Fentities-11.xhtml%23lat1', - cta: 'Click here to learn more', - image: { - height: 600, - url: 'https://blobs.omnitagjs.com/blobs/f1/f1c80d4bb5643c22/fd4362d35bb174d6f1c80d4bb5643c22', - width: 300, - }, - impressionTrackers: [ - 'https://testPixelIMP.com/fake', - 'https://tracking.omnitagjs.com/tracking/pixel?event_kind=IMPRESSION&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991', - 'https://tracking.omnitagjs.com/tracking/pixel?event_kind=INSERTION&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991' - ], - javascriptTrackers: [ - 'https://testJsIMP.com/fake.js' - ], - privacyIcon: 'https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.png', - privacyLink: 'https://blobs.omnitagjs.com/adchoice/', - sponsoredBy: 'QA Team', - title: 'Adserver Traffic Redirect Internal', - } + expect(result).to.deep.equal(nativeResult); + }); - }]); + it('receive reponse with Native ad', function () { + serverResponse.body = responseWithSingleNative; + let result = spec.interpretResponse(serverResponse, {data: '{"Bids":' + JSON.stringify(sentBidNative) + '}'}); + + expect(result.length).to.equal(1); + + const noMeta = [...nativeResult]; + const metaBackup = noMeta[0].meta; + + // this test should return default meta object + noMeta[0].meta = { advertiserDomains: [] }; + + expect(result).to.deep.equal(noMeta); + }); + + it('receive Vast reponse with Video ad', function () { + serverResponse.body = responseWithSingleVideo; + let result = spec.interpretResponse(serverResponse, {data: '{"Bids":' + JSON.stringify(sentBidVideo) + '}'}); + + expect(result.length).to.equal(1); + expect(result).to.deep.equal(videoResult); }); + + it('should expose gvlid', function() { + expect(spec.gvlid).to.equal(259) + }) }); }); diff --git a/test/spec/modules/afpBidAdapter_spec.js b/test/spec/modules/afpBidAdapter_spec.js new file mode 100644 index 00000000000..8e77a1f3e15 --- /dev/null +++ b/test/spec/modules/afpBidAdapter_spec.js @@ -0,0 +1,306 @@ +import includes from 'core-js-pure/features/array/includes.js' +import cloneDeep from 'lodash/cloneDeep' +import unset from 'lodash/unset' +import { expect } from 'chai' +import { BANNER, VIDEO } from '../../../src/mediaTypes.js' +import { + spec, + IN_IMAGE_BANNER_TYPE, + IN_IMAGE_MAX_BANNER_TYPE, + IN_CONTENT_BANNER_TYPE, + IN_CONTENT_VIDEO_TYPE, + OUT_CONTENT_VIDEO_TYPE, + IN_CONTENT_STORY_TYPE, + ACTION_SCROLLER_TYPE, + ACTION_SCROLLER_LIGHT_TYPE, + JUST_BANNER_TYPE, + BIDDER_CODE, + SSP_ENDPOINT, + REQUEST_METHOD, + TEST_PAGE_URL, + IS_DEV, mediaTypeByPlaceType +} from 'modules/afpBidAdapter.js' + +const placeId = '613221112871613d1517d181' +const bidId = '2a67c5577ff6a5' +const transactionId = '7e8515a2-2ed9-4733-b976-6c2596a03287' +const imageUrl = 'https://rtbinsight.ru/content/images/size/w1000/2021/05/ximage-30.png.pagespeed.ic.IfuX4zAEPP.png' +const placeContainer = '#container' +const imageWidth = 600 +const imageHeight = 400 +const pageUrl = IS_DEV ? TEST_PAGE_URL : 'referer' +const sizes = [[imageWidth, imageHeight]] +const bidderRequest = { + refererInfo: { referer: pageUrl }, +} +const mediaTypeBanner = { [BANNER]: {sizes: [[imageWidth, imageHeight]]} } +const mediaTypeVideo = { [VIDEO]: {playerSize: [[imageWidth, imageHeight]]} } +const commonParams = { + placeId, + placeContainer, +} +const commonParamsForInImage = Object.assign({}, commonParams, { + imageUrl, + imageWidth, + imageHeight, +}) +const configByPlaceType = { + get [IN_IMAGE_BANNER_TYPE]() { + return cloneDeep({ + mediaTypes: mediaTypeBanner, + params: Object.assign({}, commonParamsForInImage, { + placeType: IN_IMAGE_BANNER_TYPE + }), + }) + }, + get [IN_IMAGE_MAX_BANNER_TYPE]() { + return cloneDeep({ + mediaTypes: mediaTypeBanner, + params: Object.assign({}, commonParamsForInImage, { + placeType: IN_IMAGE_MAX_BANNER_TYPE + }), + }) + }, + get [IN_CONTENT_BANNER_TYPE]() { + return cloneDeep({ + mediaTypes: mediaTypeBanner, + params: Object.assign({}, commonParams, { + placeType: IN_CONTENT_BANNER_TYPE + }), + }) + }, + get [IN_CONTENT_VIDEO_TYPE]() { + return cloneDeep({ + mediaTypes: mediaTypeVideo, + params: Object.assign({}, commonParams, { + placeType: IN_CONTENT_VIDEO_TYPE + }), + }) + }, + get [OUT_CONTENT_VIDEO_TYPE]() { + return cloneDeep({ + mediaTypes: mediaTypeVideo, + params: Object.assign({}, commonParams, { + placeType: OUT_CONTENT_VIDEO_TYPE + }), + }) + }, + get [IN_CONTENT_STORY_TYPE]() { + return cloneDeep({ + mediaTypes: mediaTypeBanner, + params: Object.assign({}, commonParams, { + placeType: IN_CONTENT_STORY_TYPE + }), + }) + }, + get [ACTION_SCROLLER_TYPE]() { + return cloneDeep({ + mediaTypes: mediaTypeBanner, + params: Object.assign({}, commonParams, { + placeType: ACTION_SCROLLER_TYPE + }), + }) + }, + get [ACTION_SCROLLER_LIGHT_TYPE]() { + return cloneDeep({ + mediaTypes: mediaTypeBanner, + params: Object.assign({}, commonParams, { + placeType: ACTION_SCROLLER_LIGHT_TYPE + }), + }) + }, + get [JUST_BANNER_TYPE]() { + return cloneDeep({ + mediaTypes: mediaTypeBanner, + params: Object.assign({}, commonParams, { + placeType: JUST_BANNER_TYPE + }), + }) + }, +} +const getTransformedConfig = ({mediaTypes, params}) => { + return { + params: params, + sizes, + bidId, + bidder: BIDDER_CODE, + mediaTypes: mediaTypes, + transactionId, + } +} +const validBidRequests = Object.keys(configByPlaceType).map(key => getTransformedConfig(configByPlaceType[key])) + +describe('AFP Adapter', function() { + describe('isBidRequestValid method', function() { + describe('returns true', function() { + describe('when config has all mandatory params', () => { + Object.keys(configByPlaceType).forEach(placeType => { + it(`and ${placeType} config has the correct value`, function() { + const isBidRequestValid = spec.isBidRequestValid(configByPlaceType[placeType]) + expect(isBidRequestValid).to.equal(true) + }) + }) + }) + }) + describe('returns false', function() { + const checkMissingParams = (placesTypes, missingParams) => + placesTypes.forEach(placeType => + missingParams.forEach(missingParam => { + const config = configByPlaceType[placeType] + it(`${placeType} does not have the ${missingParam}.`, function() { + unset(config, missingParam) + const isBidRequestValid = spec.isBidRequestValid(config) + expect(isBidRequestValid).to.equal(false) + }) + }) + ) + + describe('when params are not correct', function() { + checkMissingParams(Object.keys(configByPlaceType), ['params.placeId', 'params.placeType']) + checkMissingParams([IN_IMAGE_BANNER_TYPE, IN_IMAGE_MAX_BANNER_TYPE], + ['params.imageUrl', 'params.imageWidth', 'params.imageHeight']) + + it('does not have a the correct placeType.', function() { + const config = configByPlaceType[IN_IMAGE_BANNER_TYPE] + config.params.placeType = 'something' + const isBidRequestValid = spec.isBidRequestValid(config) + expect(isBidRequestValid).to.equal(false) + }) + }) + describe('when video mediaType object is not correct.', function() { + checkMissingParams([IN_CONTENT_VIDEO_TYPE, OUT_CONTENT_VIDEO_TYPE], + [`mediaTypes.${VIDEO}.playerSize`, `mediaTypes.${VIDEO}`]) + checkMissingParams([ + IN_IMAGE_BANNER_TYPE, + IN_IMAGE_MAX_BANNER_TYPE, + IN_CONTENT_BANNER_TYPE, + IN_CONTENT_STORY_TYPE, + ACTION_SCROLLER_TYPE, + ACTION_SCROLLER_LIGHT_TYPE, + JUST_BANNER_TYPE + ], [`mediaTypes.${BANNER}.sizes`, `mediaTypes.${BANNER}`]) + }) + }) + }) + + describe('buildRequests method', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + + it('Url should be correct', function() { + expect(request.url).to.equal(SSP_ENDPOINT) + }) + + it('Method should be correct', function() { + expect(request.method).to.equal(REQUEST_METHOD) + }) + + describe('Common data request should be correct', function() { + it('pageUrl should be correct', function() { + expect(request.data.pageUrl).to.equal(pageUrl) + }) + it('bidRequests should be array', function() { + expect(Array.isArray(request.data.bidRequests)).to.equal(true) + }) + + request.data.bidRequests.forEach((bid, index) => { + describe(`bid with ${validBidRequests[index].params.placeType} should be correct`, function() { + it('bidId should be correct', function() { + expect(bid.bidId).to.equal(bidId) + }) + it('placeId should be correct', function() { + expect(bid.placeId).to.equal(placeId) + }) + it('transactionId should be correct', function() { + expect(bid.transactionId).to.equal(transactionId) + }) + it('sizes should be correct', function() { + expect(bid.sizes).to.equal(sizes) + }) + + if (includes([IN_IMAGE_BANNER_TYPE, IN_IMAGE_MAX_BANNER_TYPE], validBidRequests[index].params.placeType)) { + it('imageUrl should be correct', function() { + expect(bid.imageUrl).to.equal(imageUrl) + }) + it('imageWidth should be correct', function() { + expect(bid.imageWidth).to.equal(Math.floor(imageWidth)) + }) + it('imageHeight should be correct', function() { + expect(bid.imageHeight).to.equal(Math.floor(imageHeight)) + }) + } + }) + }) + }) + }) + + describe('interpretResponse method', function() { + it('should return a void array, when the server response are not correct.', function() { + const request = { data: JSON.stringify({}) } + const serverResponse = { + body: {} + } + const bids = spec.interpretResponse(serverResponse, request) + expect(Array.isArray(bids)).to.equal(true) + expect(bids.length).to.equal(0) + }) + it('should return a void array, when the server response have not got bids.', function() { + const request = { data: JSON.stringify({}) } + const serverResponse = { body: { bids: [] } } + const bids = spec.interpretResponse(serverResponse, request) + expect(Array.isArray(bids)).to.equal(true) + expect(bids.length).to.equal(0) + }) + describe('when the server response return a bids', function() { + Object.keys(configByPlaceType).forEach(placeType => { + it(`should return a bid with ${placeType} placeType`, function() { + const cpm = 10 + const currency = 'RUB' + const creativeId = '123' + const netRevenue = true + const width = sizes[0][0] + const height = sizes[0][1] + const adSettings = { + content: 'html' + } + const placeSettings = { + placeType, + } + const request = spec.buildRequests([validBidRequests[0]], bidderRequest) + const serverResponse = { + body: { + bids: [ + { + bidId, + cpm, + currency, + creativeId, + netRevenue, + width, + height, + adSettings, + placeSettings, + } + ] + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal(bidId) + expect(bids[0].meta.mediaType).to.equal(mediaTypeByPlaceType[placeSettings.placeType]) + expect(bids[0].cpm).to.equal(cpm) + expect(bids[0].width).to.equal(width) + expect(bids[0].height).to.equal(height) + expect(bids[0].currency).to.equal(currency) + expect(bids[0].netRevenue).to.equal(netRevenue) + + if (mediaTypeByPlaceType[placeSettings.placeType] === BANNER) { + expect(typeof bids[0].ad).to.equal('string') + } else if (mediaTypeByPlaceType[placeSettings.placeType] === VIDEO) { + expect(typeof bids[0].vastXml).to.equal('string') + expect(typeof bids[0].renderer).to.equal('object') + } + }) + }) + }) + }) +}) diff --git a/test/spec/modules/airgridRtdProvider_spec.js b/test/spec/modules/airgridRtdProvider_spec.js new file mode 100644 index 00000000000..cc10dda4ad1 --- /dev/null +++ b/test/spec/modules/airgridRtdProvider_spec.js @@ -0,0 +1,97 @@ +import {config} from 'src/config.js'; +import {deepAccess} from 'src/utils.js' +import {getAdUnits} from '../../fixtures/fixtures.js'; +import * as agRTD from 'modules/airgridRtdProvider.js'; + +const MATCHED_AUDIENCES = ['travel', 'sport']; +const RTD_CONFIG = { + auctionDelay: 250, + dataProviders: [{ + name: 'airgrid', + waitForIt: true, + params: { + apiKey: 'key123', + accountId: 'sdk', + publisherId: 'pub123', + bidders: ['pubmatic'] + } + }] +}; + +describe('airgrid RTD Submodule', function() { + let getDataFromLocalStorageStub; + + beforeEach(function() { + config.resetConfig(); + getDataFromLocalStorageStub = sinon.stub(agRTD.storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + describe('Initialise module', function() { + it('should initalise and return true', function () { + expect(agRTD.airgridSubmodule.init(RTD_CONFIG.dataProviders[0])).to.equal(true); + }); + it('should attach script to DOM with correct config', function() { + agRTD.attachScriptTagToDOM(RTD_CONFIG); + expect(window.edktInitializor.invoked).to.be.true; + expect(window.edktInitializor.apiKey).to.equal(RTD_CONFIG.dataProviders[0].params.apiKey); + expect(window.edktInitializor.accountId).to.equal(RTD_CONFIG.dataProviders[0].params.accountId); + expect(window.edktInitializor.publisherId).to.equal(RTD_CONFIG.dataProviders[0].params.publisherId); + }); + }); + + describe('Get matched audiences', function() { + it('gets matched audiences from local storage', function() { + getDataFromLocalStorageStub.withArgs(agRTD.AG_AUDIENCE_IDS_KEY).returns(JSON.stringify(MATCHED_AUDIENCES)); + + const audiences = agRTD.getMatchedAudiencesFromStorage(); + expect(audiences).to.have.members(MATCHED_AUDIENCES); + }); + }); + + describe('Add matched audiences', function() { + it('merges matched audiences on appnexus AdUnits', function() { + const adUnits = getAdUnits(); + getDataFromLocalStorageStub.withArgs(agRTD.AG_AUDIENCE_IDS_KEY).returns(JSON.stringify(MATCHED_AUDIENCES)); + agRTD.passAudiencesToBidders({ adUnits }, () => {}, {}, {}); + + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid; + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.perid')).to.eql(MATCHED_AUDIENCES); + } + }); + }); + }); + it('does not merge audiences on appnexus adunits, since none are matched', function() { + const adUnits = getAdUnits(); + getDataFromLocalStorageStub.withArgs(agRTD.AG_AUDIENCE_IDS_KEY).returns(undefined); + agRTD.passAudiencesToBidders({ adUnits }, () => {}, {}, {}); + + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid; + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.perid')).to.be.undefined; + } + }); + }); + }); + it('sets bidder specific ORTB2 config', function() { + getDataFromLocalStorageStub.withArgs(agRTD.AG_AUDIENCE_IDS_KEY).returns(JSON.stringify(MATCHED_AUDIENCES)); + const audiences = agRTD.getMatchedAudiencesFromStorage(); + agRTD.setAudiencesUsingBidderOrtb2(RTD_CONFIG.dataProviders[0], audiences); + + const allBiddersConfig = config.getBidderConfig(); + const bidders = RTD_CONFIG.dataProviders[0].params.bidders; + Object.keys(allBiddersConfig).forEach((bidder) => { + if (bidders.indexOf(bidder) === -1) return; + expect(deepAccess(allBiddersConfig[bidder], 'ortb2.user.ext.data.airgrid')).to.eql(MATCHED_AUDIENCES); + }); + }); + }); +}); diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index 80ecab764e8..9bb77520212 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -34,21 +34,21 @@ describe('AjaAdapter', function () { }); describe('buildRequests', function () { - let bidRequests = [ + const bidRequests = [ { - 'bidder': 'aja', - 'params': { - 'asi': '123456' + bidder: 'aja', + params: { + asi: '123456' }, - 'adUnitCode': 'adunit', - 'sizes': [[300, 250]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + adUnitCode: 'adunit', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', } ]; - let bidderRequest = { + const bidderRequest = { refererInfo: { referer: 'https://hoge.com' } @@ -62,6 +62,44 @@ describe('AjaAdapter', function () { }); }); + describe('buildRequests with UserModule', function () { + const bidRequests = [ + { + bidder: 'aja', + params: { + asi: '123456' + }, + adUnitCode: 'adunit', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userIdAsEids: [ + { + source: 'pubcid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + } + ] + } + ]; + + const bidderRequest = { + refererInfo: { + referer: 'https://hoge.com' + } + }; + + it('sends bid request to ENDPOINT via GET', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].url).to.equal(ENDPOINT); + expect(requests[0].method).to.equal('GET'); + expect(requests[0].data).to.equal('asi=123456&skt=5&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&eids=%7B%22eids%22%3A%5B%7B%22source%22%3A%22pubcid.org%22%2C%22uids%22%3A%5B%7B%22id%22%3A%22some-random-id-value%22%2C%22atype%22%3A1%7D%5D%7D%5D%7D&'); + }); + }); + describe('interpretResponse', function () { it('should get correct banner bid response', function () { let response = { @@ -78,8 +116,11 @@ describe('AjaAdapter', function () { 'tag': '
', 'imps': [ 'https://as.amanad.adtdp.com/v1/imp' + ], + 'adomain': [ + 'www.example.com' ] - } + }, }, 'syncs': [ 'https://example.com' @@ -98,7 +139,12 @@ describe('AjaAdapter', function () { 'mediaType': 'banner', 'currency': 'USD', 'ttl': 300, - 'netRevenue': true + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [ + 'www.example.com' + ] + } } ]; @@ -123,7 +169,10 @@ describe('AjaAdapter', function () { 'purl': 'https://cdn/player', 'progress': true, 'loop': false, - 'inread': false + 'inread': false, + 'adomain': [ + 'www.example.com' + ] } }, 'syncs': [ @@ -178,7 +227,10 @@ describe('AjaAdapter', function () { 'https://example.com/inview' ], 'jstracker': '', - 'disable_trimming': false + 'disable_trimming': false, + 'adomain': [ + 'www.example.com' + ] } ] } @@ -218,7 +270,12 @@ describe('AjaAdapter', function () { 'impressionTrackers': [ 'https://example.com/imp' ], - 'privacyLink': 'https://aja-kk.co.jp/optout', + 'privacyLink': 'https://aja-kk.co.jp/optout' + }, + 'meta': { + 'advertiserDomains': [ + 'www.example.com' + ] } } ]; diff --git a/test/spec/modules/akamaiDAPIdSystem_spec.js b/test/spec/modules/akamaiDAPIdSystem_spec.js new file mode 100644 index 00000000000..e44285eda34 --- /dev/null +++ b/test/spec/modules/akamaiDAPIdSystem_spec.js @@ -0,0 +1,117 @@ +import {akamaiDAPIdSubmodule} from 'modules/akamaiDAPIdSystem.js'; +import * as utils from 'src/utils.js'; +import {server} from 'test/mocks/xhr.js'; +import {getStorageManager} from '../../../src/storageManager.js'; + +export const storage = getStorageManager(); + +const signatureConfigParams = {params: { + apiHostname: 'prebid.dap.akadns.net', + domain: 'prebid.org', + type: 'dap-signature:1.0.0', + apiVersion: 'v1' +}}; + +const tokenizeConfigParams = {params: { + apiHostname: 'prebid.dap.akadns.net', + domain: 'prebid.org', + type: 'email', + identity: 'amishra@xyz.com', + apiVersion: 'v1' +}}; + +const x1TokenizeConfigParams = {params: { + apiHostname: 'prebid.dap.akadns.net', + domain: 'prebid.org', + type: 'email', + identity: 'amishra@xyz.com', + apiVersion: 'x1', + attributes: '{ "cohorts": [ "3:14400", "5:14400", "7:0" ],"first_name": "Ace","last_name": "McCool" }' +}}; + +const consentData = { + gdprApplies: true, + consentString: 'BOkIpDSOkIpDSADABAENCc-AAAApOAFAAMAAsAMIAcAA_g' +}; + +const responseHeader = {'Content-Type': 'application/json'} + +const TEST_ID = '51sd61e3-sd82-4vea-8387-093dffca4a3a'; + +describe('akamaiDAPId getId', function () { + let logErrorStub; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + logErrorStub.restore(); + }); + + describe('decode', function () { + it('should respond with an object with dapId containing the value', () => { + expect(akamaiDAPIdSubmodule.decode(TEST_ID)).to.deep.equal({ + dapId: TEST_ID + }); + }); + }); + + describe('getId', function () { + it('should log an error if no configParams were passed when getId', function () { + akamaiDAPIdSubmodule.getId(null); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should log an error if configParams were passed without apihostname', function () { + akamaiDAPIdSubmodule.getId({ params: { + domain: 'prebid.org', + type: 'dap-signature:1.0.0' + } }); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should log an error if configParams were passed without domain', function () { + akamaiDAPIdSubmodule.getId({ params: { + apiHostname: 'prebid.dap.akadns.net', + type: 'dap-signature:1.0.0' + } }); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should log an error if configParams were passed without type', function () { + akamaiDAPIdSubmodule.getId({ params: { + apiHostname: 'prebid.dap.akadns.net', + domain: 'prebid.org' + } }); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('akamaiDAPId submobile requires consent string to call API', function () { + let consentData = { + gdprApplies: true, + consentString: '' + }; + let submoduleCallback = akamaiDAPIdSubmodule.getId(signatureConfigParams, consentData); + expect(submoduleCallback).to.be.undefined; + }); + + it('should call the signature v1 API and store token in Local storage', function () { + let submoduleCallback1 = akamaiDAPIdSubmodule.getId(signatureConfigParams, consentData).id; + expect(submoduleCallback1).to.be.eq(storage.getDataFromLocalStorage('akamai_dap_token')) + storage.removeDataFromLocalStorage('akamai_dap_token'); + }); + + it('should call the tokenize v1 API and store token in Local storage', function () { + let submoduleCallback = akamaiDAPIdSubmodule.getId(tokenizeConfigParams, consentData).id; + expect(submoduleCallback).to.be.eq(storage.getDataFromLocalStorage('akamai_dap_token')) + storage.removeDataFromLocalStorage('akamai_dap_token'); + }); + + it('should call the tokenize x1 API and store token in Local storage', function () { + let submoduleCallback = akamaiDAPIdSubmodule.getId(x1TokenizeConfigParams, consentData).id; + expect(submoduleCallback).to.be.eq(storage.getDataFromLocalStorage('akamai_dap_token')) + storage.removeDataFromLocalStorage('akamai_dap_token'); + }); + }); +}); diff --git a/test/spec/modules/akamaiDapRtdProvider_spec.js b/test/spec/modules/akamaiDapRtdProvider_spec.js new file mode 100644 index 00000000000..b350c2bb529 --- /dev/null +++ b/test/spec/modules/akamaiDapRtdProvider_spec.js @@ -0,0 +1,246 @@ +import {config} from 'src/config.js'; +import {SEGMENTS_STORAGE_KEY, TOKEN_STORAGE_KEY, dapUtils, addRealTimeData, getRealTimeData, akamaiDapRtdSubmodule, storage} from 'modules/akamaiDapRtdProvider.js'; +import {server} from 'test/mocks/xhr.js'; +import logMessage from 'src/utils.js' +const responseHeader = {'Content-Type': 'application/json'}; + +describe('akamaiDapRtdProvider', function() { + let getDataFromLocalStorageStub; + let getDapTokenStub; + + const testReqBidsConfigObj = { + adUnits: [ + { + bids: ['bid1', 'bid2'] + } + ] + }; + + const onDone = function() { return true }; + + const onSuccess = function() { return ('request', 200, 'success') }; + + const cmoduleConfig = { + 'name': 'dap', + 'waitForIt': true, + 'params': { + 'apiHostname': 'prebid.dap.akadns.net', + 'apiVersion': 'x1', + 'domain': 'prebid.org', + 'identityType': 'dap-signature:1.0.0', + 'segtax': 503, + 'tokenTtl': 5 + } + } + + const sampleConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x1', + 'domain': 'prebid.org', + 'segtax': 503 + } + const sampleIdentity = { + type: 'dap-signature:1.0.0' + }; + + beforeEach(function() { + config.resetConfig(); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + describe('akamaiDapRtdSubmodule', function() { + it('successfully instantiates', function () { + expect(akamaiDapRtdSubmodule.init()).to.equal(true); + }); + }); + + describe('Get Real-Time Data', function() { + it('gets rtd from local storage cache', function() { + const rtdConfig = { + params: { + segmentCache: true + } + }; + + const bidConfig = {}; + + const rtdUserObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const cachedRtd = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj1] + } + } + } + }; + + getDataFromLocalStorageStub.withArgs(SEGMENTS_STORAGE_KEY).returns(JSON.stringify(cachedRtd)); + + expect(config.getConfig().ortb2).to.be.undefined; + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + }); + + it('should initalise and return with config', function () { + expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) + }); + }); + + describe('dapTokenize', function () { + it('dapTokenize error callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleConfig)); + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, + function(token, status, xhr) { + }, + function(xhr, status, error) { + } + ); + let request = server.requests[0]; + request.respond(400, responseHeader, JSON.stringify('error')); + expect(submoduleCallback).to.equal(undefined); + }); + + it('dapTokenize success callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleConfig)); + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, + function(token, status, xhr) { + }, + function(xhr, status, error) { + } + ); + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify('success')); + expect(submoduleCallback).to.equal(undefined); + }); + }); + + describe('dapTokenize and dapMembership incorrect params', function () { + it('Onerror and config are null', function () { + expect(dapUtils.dapTokenize(null, 'identity', null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(null, 'identity', null, null)).to.be.equal(undefined); + const config = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 1, + 'domain': '', + 'segtax': 503 + }; + let identity = { + type: 'dap-signature:1.0.0' + }; + expect(dapUtils.dapTokenize(config, identity, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(config, 'token', null, null)).to.be.equal(undefined); + }); + + it('dapGetToken success', function () { + let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns(onSuccess); + expect(dapUtils.dapGetToken(sampleConfig, 'token', + function(token, status, xhr) { + }, + function(xhr, status, error) { + } + )).to.be.equal(null); + dapTokenizeStub.restore(); + }); + }); + + describe('dapMembership', function () { + it('dapMembership success callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleConfig)); + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', + function(token, status, xhr) { + }, + function(xhr, status, error) { + } + ); + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify('success')); + expect(submoduleCallback).to.equal(undefined); + }); + + it('dapMembership error callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleConfig)); + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', + function(token, status, xhr) { + }, + function(xhr, status, error) { + } + ); + let request = server.requests[0]; + request.respond(400, responseHeader, JSON.stringify('error')); + expect(submoduleCallback).to.equal(undefined); + }); + }); + + describe('dapMembership', function () { + it('should invoke the getDapToken and getDapMembership', function () { + let config = { + api_hostname: cmoduleConfig.params.apiHostname, + api_version: cmoduleConfig.params.apiVersion, + domain: cmoduleConfig.params.domain, + segtax: cmoduleConfig.params.segtax + }; + let identity = { + type: cmoduleConfig.params.identityType + }; + + let membership = { + said: 'item.said1', + cohorts: 'item.cohorts', + attributes: null + }; + + let getDapTokenStub = sinon.stub(dapUtils, 'dapGetToken').returns('token3'); + let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembership').returns(membership); + let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns('response', 200, 'request'); + getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); + expect(getDapTokenStub.calledOnce).to.be.equal(true); + expect(getDapMembershipStub.calledOnce).to.be.equal(true); + getDapTokenStub.restore(); + getDapMembershipStub.restore(); + dapTokenizeStub.restore(); + }); + }); + + describe('dapMembershipToRtbSegment', function () { + it('dapMembershipToRtbSegment', function () { + let membership1 = { + said: 'item.said1', + cohorts: 'item.cohorts', + attributes: null + }; + const config = { + apiHostname: 'prebid.dap.akadns.net', + apiVersion: 'x1', + domain: 'prebid.org', + tokenTtl: 5, + segtax: 503 + }; + let identity = { + type: 'dap-signature:1.0.0' + }; + + expect(dapUtils.dapGetMembership(config, 'token')).to.equal(null) + const membership = {cohorts: ['1', '5', '7']} + expect(dapUtils.dapMembershipToRtbSegment(membership, config)).to.not.equal(undefined); + }); + }); +}); diff --git a/test/spec/modules/amxIdSystem_spec.js b/test/spec/modules/amxIdSystem_spec.js new file mode 100644 index 00000000000..dea79e87baa --- /dev/null +++ b/test/spec/modules/amxIdSystem_spec.js @@ -0,0 +1,202 @@ +import { amxIdSubmodule } from 'modules/amxIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; +import * as utils from 'src/utils.js'; + +const TEST_ID = '51b561e3-0d82-4aea-8487-093fffca4a3a'; +const ERROR_CODES = [404, 501, 500, 403]; + +const config = { + params: { + tagId: Math.floor(Math.random() * 9e9).toString(36), + }, + storage: { + type: 'html5', + }, +}; + +describe('amxid submodule', () => { + it('should expose a "name" property containing amxId', () => { + expect(amxIdSubmodule.name).to.equal('amxId'); + }); + + it('should expose a "gvlid" property containing the GVL ID 737', () => { + expect(amxIdSubmodule.gvlid).to.equal(737); + }); +}); + +describe('decode', () => { + it('should respond with an object with "amxId" key containing the value', () => { + expect(amxIdSubmodule.decode(TEST_ID)).to.deep.equal({ + amxId: TEST_ID + }); + }); + + it('should respond with undefined if the value is not a string', () => { + [1, null, undefined, NaN, [], {}].forEach((value) => { + expect(amxIdSubmodule.decode(value)).to.equal(undefined); + }); + }); +}); + +describe('validateConfig', () => { + let logErrorSpy; + + beforeEach(() => { + logErrorSpy = sinon.spy(utils, 'logError'); + }); + afterEach(() => { + logErrorSpy.restore(); + }); + + it('should return undefined if config.storage is not present', () => { + expect( + amxIdSubmodule.getId( + { + ...config, + storage: null, + }, + null, + null + ) + ).to.equal(undefined); + + expect(logErrorSpy.calledOnce).to.be.true; + expect(logErrorSpy.lastCall.lastArg).to.contain('storage is required'); + }); + + it('should return undefined if config.storage.type !== "html5"', () => { + expect( + amxIdSubmodule.getId( + { + ...config, + storage: { + type: 'cookie', + }, + }, + null, + null + ) + ).to.equal(undefined); + + expect(logErrorSpy.calledOnce).to.be.true; + expect(logErrorSpy.lastCall.lastArg).to.contain('cookie'); + }); + + it('should return undefined if expires > 30', () => { + const expires = Math.floor(Math.random() * 90) + 30.01; + expect( + amxIdSubmodule.getId( + { + ...config, + storage: { + type: 'html5', + expires, + }, + }, + null, + null + ) + ).to.equal(undefined); + + expect(logErrorSpy.calledOnce).to.be.true; + expect(logErrorSpy.lastCall.lastArg).to.contain(expires); + }); +}); + +describe('getId', () => { + const spy = sinon.spy(); + + beforeEach(() => { + spy.resetHistory(); + }); + + it('should call the sync endpoint and accept a valid response', () => { + const { callback } = amxIdSubmodule.getId(config, null, null); + callback(spy); + + const [request] = server.requests; + expect(request.method).to.equal('GET'); + + request.respond( + 200, + {}, + JSON.stringify({ + id: TEST_ID, + v: '1.0a', + }) + ); + + expect(spy.calledOnce).to.be.true; + expect(spy.lastCall.lastArg).to.equal(TEST_ID); + }); + + it('should return undefined if the server has an error status code', () => { + const { callback } = amxIdSubmodule.getId(config, null, null); + callback(spy); + + const [request] = server.requests; + const responseCode = + ERROR_CODES[Math.floor(Math.random() * ERROR_CODES.length)]; + request.respond(responseCode, {}, ''); + + expect(spy.calledOnce).to.be.true; + expect(spy.lastCall.lastArg).to.equal(undefined); + }); + + it('should return undefined if the response has invalid keys', () => { + const { callback } = amxIdSubmodule.getId(config, null, null); + callback(spy); + + const [request] = server.requests; + request.respond( + 200, + {}, + JSON.stringify({ + test: TEST_ID, + }) + ); + + expect(spy.calledOnce).to.be.true; + expect(spy.lastCall.lastArg).to.equal(undefined); + }); + + it('should returned undefined if the server JSON is invalid', () => { + const { callback } = amxIdSubmodule.getId(config, null, null); + callback(spy); + + const [request] = server.requests; + request.respond(200, {}, '{,,}'); + + expect(spy.calledOnce).to.be.true; + expect(spy.lastCall.lastArg).to.equal(undefined); + }); + + it('should use the intermediate value for the sync server', () => { + const { callback } = amxIdSubmodule.getId(config, null, null); + callback(spy); + + const [request] = server.requests; + const intermediateValue = 'https://example-publisher.com/api/sync'; + + request.respond( + 200, + {}, + JSON.stringify({ + u: intermediateValue, + }) + ); + + const [, secondRequest] = server.requests; + expect(secondRequest.url).to.be.equal(intermediateValue); + secondRequest.respond( + 200, + {}, + JSON.stringify({ + id: TEST_ID, + }) + ); + + expect(spy.calledOnce).to.be.true; + expect(spy.lastCall.lastArg).to.equal(TEST_ID); + }); +}); diff --git a/test/spec/modules/aniviewBidAdapter_spec.js b/test/spec/modules/aniviewBidAdapter_spec.js index 2e1fdb56201..a9498af046c 100644 --- a/test/spec/modules/aniviewBidAdapter_spec.js +++ b/test/spec/modules/aniviewBidAdapter_spec.js @@ -160,6 +160,7 @@ describe('ANIVIEW Bid Adapter Test', function () { expect(bidResponse.currency).to.equal('USD'); expect(bidResponse.netRevenue).to.equal(true); expect(bidResponse.mediaType).to.equal('video'); + expect(bidResponse.meta.advertiserDomains).to.be.an('array').that.is.empty; }); it('safely handles XML parsing failure from invalid bid response', function () { @@ -203,16 +204,43 @@ describe('ANIVIEW Bid Adapter Test', function () { expect(bidResponse.renderer.loaded).to.equal(false) expect(bidResponse.width).to.equal(640) expect(bidResponse.height).to.equal(480) + }); + + it('Support banner format', function () { + const bidRequest = spec.buildRequests([ + { + bidId: '253dcb69fb2577', + params: { + playerDomain: 'example.com', + AV_PUBLISHERID: '55b78633181f4603178b4568', + AV_CHANNELID: '55b7904d181f46410f8b4568' + }, + mediaTypes: { + banner: { + sizes: [[640, 480]], + } + } + } + ])[0] + const bidResponse = spec.interpretResponse(serverResponse, bidRequest)[0] + + expect(bidResponse.ad).to.have.string('https://example.com/script/6.1/prebidRenderer.js'); + expect(bidResponse.width).to.equal(640) + expect(bidResponse.height).to.equal(480) }) }); describe('getUserSyncs', function () { - it('Check get sync pixels from response', function () { - let pixelUrl = 'https://sync.pixel.url/sync'; + let pixelUrl = 'https://sync.pixel.url/sync'; + function createBidResponse (pixelEvent, pixelType) { + let pixelStr = '{"url":"' + pixelUrl + '", "e":"' + pixelEvent + '", "t":' + pixelType + '}'; + return 'FORDFORD00:00:15'; + } + + it('Check get iframe sync pixels from response on inventory', function () { let pixelEvent = 'inventory'; let pixelType = '3'; - let pixelStr = '{"url":"' + pixelUrl + '", "e":"' + pixelEvent + '", "t":' + pixelType + '}'; - let bidResponse = 'FORDFORD00:00:15'; + let bidResponse = createBidResponse(pixelEvent, pixelType); let serverResponse = [ {body: bidResponse} ]; @@ -222,5 +250,19 @@ describe('ANIVIEW Bid Adapter Test', function () { expect(pixel.url).to.equal(pixelUrl); expect(pixel.type).to.equal('iframe'); }); + + it('Check get image sync pixels from response on sync', function () { + let pixelEvent = 'sync'; + let pixelType = '1'; + let bidResponse = createBidResponse(pixelEvent, pixelType); + let serverResponse = [ + {body: bidResponse} + ]; + let syncPixels = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); + expect(syncPixels.length).to.equal(1); + let pixel = syncPixels[0]; + expect(pixel.url).to.equal(pixelUrl); + expect(pixel.type).to.equal('image'); + }); }); }); diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index 78be0618594..50622180ad0 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -369,18 +369,6 @@ describe('AolAdapter', function () { expect(request.url).not.to.contain('bidfloor='); }); - it('should return url with bidFloor option if it is present', function () { - let bidRequest = createCustomBidRequest({ - params: { - placement: 1234567, - network: '9599.1', - bidFloor: 0.80 - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('bidfloor=0.8'); - }); - it('should return url with key values if keyValues param is present', function () { let bidRequest = createCustomBidRequest({ params: { @@ -781,13 +769,6 @@ describe('AolAdapter', function () { }); expect(spec.formatMarketplaceDynamicParams()).to.be.equal('param1=val1;param2=val2;param3=val3;'); }); - - it('should return formatted bid floor param when it is present', function () { - let params = { - bidFloor: 0.45 - }; - expect(spec.formatMarketplaceDynamicParams(params)).to.be.equal('bidfloor=0.45;'); - }); }); describe('formatOneMobileDynamicParams()', function () { diff --git a/test/spec/modules/apacdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js index 3a71833bc3e..9b75481ada9 100644 --- a/test/spec/modules/apacdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec, validateGeoObject, getDomain } from '../../../modules/apacdexBidA import { newBidder } from 'src/adapters/bidderFactory.js' import { userSync } from '../../../src/userSync.js'; import { config } from 'src/config.js'; +import { deepClone } from 'src/utils.js'; describe('ApacdexBidAdapter', function () { const adapter = newBidder(spec) @@ -36,12 +37,13 @@ describe('ApacdexBidAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false if there is no siteId param', () => { + it('should return false if there is no siteId or placementId param', () => { const bid = { 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { site_id: '1a2b3c4d5e6f1a2b3c4d', + placement_id: 'plcm12345678', }, 'mediaTypes': { banner: { @@ -200,7 +202,7 @@ describe('ApacdexBidAdapter', function () { 'bidder': 'apacdex', 'params': { 'siteId': '1a2b3c4d5e6f1a2b3c4d', - 'geo': {'lat': 123.13123456, 'lon': 54.23467311, 'accuracy': 60} + 'geo': { 'lat': 123.13123456, 'lon': 54.23467311, 'accuracy': 60 } }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], @@ -218,15 +220,6 @@ describe('ApacdexBidAdapter', function () { 'id': '2ae366c2-2576-45e5-bd21-72ed10598f17', 'atype': 1 }] - }, { - 'source': 'sharedid.org', - 'uids': [{ - 'id': '01EZXQDVAPER4KE1VBS29XKV4Z', - 'atype': 1, - 'ext': { - 'third': '01EZXQDVAPER4KE1VBS29XKV4Z' - } - }] }], }, { @@ -336,12 +329,50 @@ describe('ApacdexBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.us_privacy).to.equal('someCCPAString'); }); - describe('debug test', function() { - beforeEach(function() { - config.setConfig({debug: true}); + it('should attach bidFloor param when either bid param floorPrice or getFloor function exists', function () { + let getFloorResponse = { currency: 'USD', floor: 3 }; + let singleBidRequest, request, payload = null; + + // 1 -> floorPrice not defined, getFloor not defined > empty + singleBidRequest = deepClone(bidRequest[0]); + request = spec.buildRequests([singleBidRequest], bidderRequests); + payload = request.data; + expect(payload.bids[0].bidFloor).to.not.exist; + + // 2 -> floorPrice is defined, getFloor not defined > floorPrice is used + singleBidRequest = deepClone(bidRequest[0]); + singleBidRequest.params = { + 'siteId': '1890909', + 'floorPrice': 0.5 + }; + request = spec.buildRequests([singleBidRequest], bidderRequests); + payload = request.data + expect(payload.bids[0].bidFloor).to.exist.and.to.equal(0.5); + + // 3 -> floorPrice is defined, getFloor is defined > getFloor is used + singleBidRequest = deepClone(bidRequest[0]); + singleBidRequest.params = { + 'siteId': '1890909', + 'floorPrice': 0.5 + }; + singleBidRequest.getFloor = () => getFloorResponse; + request = spec.buildRequests([singleBidRequest], bidderRequests); + payload = request.data + expect(payload.bids[0].bidFloor).to.exist.and.to.equal(3); + + // 4 -> floorPrice not defined, getFloor is defined > getFloor is used + singleBidRequest = deepClone(bidRequest[0]); + singleBidRequest.getFloor = () => getFloorResponse; + request = spec.buildRequests([singleBidRequest], bidderRequests); + payload = request.data + expect(payload.bids[0].bidFloor).to.exist.and.to.equal(3); + }); + describe('debug test', function () { + beforeEach(function () { + config.setConfig({ debug: true }); }); - afterEach(function() { - config.setConfig({debug: false}); + afterEach(function () { + config.setConfig({ debug: false }); }); it('should return a properly formatted request with pbjs_debug is true', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); @@ -514,7 +545,10 @@ describe('ApacdexBidAdapter', function () { 'netRevenue': true, 'currency': 'USD', 'dealId': 'apacdex', - 'mediaType': 'banner' + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['https://example.com'] + } }, { 'requestId': '30024615be22ef66a', @@ -527,7 +561,10 @@ describe('ApacdexBidAdapter', function () { 'netRevenue': true, 'currency': 'USD', 'dealId': 'apacdex', - 'mediaType': 'banner' + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['https://example.com'] + } }, { 'requestId': '1854b40107d6745c', @@ -540,7 +577,10 @@ describe('ApacdexBidAdapter', function () { 'netRevenue': true, 'currency': 'USD', 'dealId': 'apacdex', - 'mediaType': 'video' + 'mediaType': 'video', + 'meta': { + 'advertiserDomains': ['https://example.com'] + } } ], 'pixel': [{ @@ -610,6 +650,7 @@ describe('ApacdexBidAdapter', function () { if (resp.mediaType === 'banner') { expect(resp.ad.indexOf('Apacdex AD')).to.be.greaterThan(0); } + expect(resp.meta.advertiserDomains).to.deep.equal(['https://example.com']); }); }); }); @@ -693,17 +734,17 @@ describe('ApacdexBidAdapter', function () { describe('getDomain', function () { it('should return valid domain from publisherDomain config', () => { let pageUrl = 'https://www.example.com/page/prebid/exam.html'; - config.setConfig({publisherDomain: pageUrl}); + config.setConfig({ publisherDomain: pageUrl }); expect(getDomain(pageUrl)).to.equal('example.com'); }); it('should return valid domain from pageUrl argument', () => { let pageUrl = 'https://www.example.com/page/prebid/exam.html'; - config.setConfig({publisherDomain: ''}); + config.setConfig({ publisherDomain: '' }); expect(getDomain(pageUrl)).to.equal('example.com'); }); it('should return undefined if pageUrl and publisherDomain not config', () => { let pageUrl; - config.setConfig({publisherDomain: ''}); + config.setConfig({ publisherDomain: '' }); expect(getDomain(pageUrl)).to.equal(pageUrl); }); }); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 919b36ff71a..2cf65bc8a59 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -215,6 +215,40 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].hb_source).to.deep.equal(1); }); + it('should include ORTB video values when video params were not set', function() { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + it('should add video property when adUnit includes a renderer', function () { const videoData = { mediaTypes: { @@ -522,6 +556,33 @@ describe('AppNexusAdapter', function () { config.getConfig.restore(); }); + it('adds auction level keywords to request when set', function() { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon + .stub(config, 'getConfig') + .withArgs('appnexusAuctionKeywords') + .returns({ + gender: 'm', + music: ['rock', 'pop'], + test: '' + }); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.keywords).to.deep.equal([{ + 'key': 'gender', + 'value': ['m'] + }, { + 'key': 'music', + 'value': ['rock', 'pop'] + }, { + 'key': 'test' + }]); + + config.getConfig.restore(); + }); + it('should attach native params to the request', function () { let bidRequest = Object.assign({}, bidRequests[0], @@ -679,7 +740,8 @@ describe('AppNexusAdapter', function () { 'timeout': 3000, 'gdprConsent': { consentString: consentString, - gdprApplies: true + gdprApplies: true, + addtlConsent: '1~7.12.35.62.66.70.89.93.108' } }; bidderRequest.bids = bidRequests; @@ -691,6 +753,7 @@ describe('AppNexusAdapter', function () { expect(payload.gdpr_consent).to.exist; expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString); expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; + expect(payload.gdpr_consent.addtl_consent).to.exist.and.to.deep.equal([7, 12, 35, 62, 66, 70, 89, 93, 108]); }); it('should add us privacy string to payload', function() { @@ -873,9 +936,14 @@ describe('AppNexusAdapter', function () { const bidRequest = Object.assign({}, bidRequests[0], { userId: { tdid: 'sample-userid', + uid2: { id: 'sample-uid2-value' }, criteoId: 'sample-criteo-userid', netId: 'sample-netId-userid', - idl_env: 'sample-idl-userid' + idl_env: 'sample-idl-userid', + flocId: { + id: 'sample-flocid-value', + version: 'chrome.1.0' + } } }); @@ -892,6 +960,11 @@ describe('AppNexusAdapter', function () { id: 'sample-criteo-userid', }); + expect(payload.eids).to.deep.include({ + source: 'chrome.com', + id: 'sample-flocid-value' + }); + expect(payload.eids).to.deep.include({ source: 'netid.de', id: 'sample-netId-userid', @@ -900,7 +973,13 @@ describe('AppNexusAdapter', function () { expect(payload.eids).to.deep.include({ source: 'liveramp.com', id: 'sample-idl-userid' - }) + }); + + expect(payload.eids).to.deep.include({ + source: 'uidapi.com', + id: 'sample-uid2-value', + rti_partner: 'UID2' + }); }); it('should populate iab_support object at the root level if omid support is detected', function () { @@ -952,12 +1031,16 @@ describe('AppNexusAdapter', function () { describe('interpretResponse', function () { let bfStub; + let bidderSettingsStorage; + before(function() { bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); + bidderSettingsStorage = $$PREBID_GLOBAL$$.bidderSettings; }); after(function() { bfStub.restore(); + $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsStorage; }); let response = { @@ -995,7 +1078,8 @@ describe('AppNexusAdapter', function () { 'trackers': [ { 'impression_urls': [ - 'https://lax1-ib.adnxs.com/impression' + 'https://lax1-ib.adnxs.com/impression', + 'https://www.test.com/tracker' ], 'video_events': {} } @@ -1024,6 +1108,15 @@ describe('AppNexusAdapter', function () { 'adUnitCode': 'code', 'appnexus': { 'buyerMemberId': 958 + }, + 'meta': { + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [{ + 'bsid': '958' + }] + } } } ]; @@ -1032,11 +1125,46 @@ describe('AppNexusAdapter', function () { bidId: '3db3773286ee59', adUnitCode: 'code' }] - } + }; let result = spec.interpretResponse({ body: response }, {bidderRequest}); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); + it('should reject 0 cpm bids', function () { + let zeroCpmResponse = deepClone(response); + zeroCpmResponse.tags[0].ads[0].cpm = 0; + + let bidderRequest = { + bidderCode: 'appnexus' + }; + + let result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); + expect(result.length).to.equal(0); + }); + + it('should allow 0 cpm bids if allowZeroCpmBids setConfig is true', function () { + $$PREBID_GLOBAL$$.bidderSettings = { + appnexus: { + allowZeroCpmBids: true + } + }; + + let zeroCpmResponse = deepClone(response); + zeroCpmResponse.tags[0].ads[0].cpm = 0; + + let bidderRequest = { + bidderCode: 'appnexus', + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + }; + + let result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); + expect(result.length).to.equal(1); + expect(result[0].cpm).to.equal(0); + }); + it('handles nobid responses', function () { let response = { 'version': '0.0.1', @@ -1280,6 +1408,35 @@ describe('AppNexusAdapter', function () { } let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); - }) + }); + + it('should add brand id', function() { + let responseBrandId = deepClone(response); + responseBrandId.tags[0].ads[0].brand_id = 123; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseBrandId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['brandId']); + }); + + it('should add advertiserDomains', function() { + let responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].adomain = ['123']; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['advertiserDomains']); + expect(Object.keys(result[0].meta.advertiserDomains)).to.deep.equal([]); + }); }); }); diff --git a/test/spec/modules/asealBidAdapter_spec.js b/test/spec/modules/asealBidAdapter_spec.js new file mode 100644 index 00000000000..ef74dd3fc05 --- /dev/null +++ b/test/spec/modules/asealBidAdapter_spec.js @@ -0,0 +1,144 @@ +import { expect } from 'chai'; +import { spec, BIDDER_CODE, API_ENDPOINT, HEADER_AOTTER_VERSION } from 'modules/asealBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; + +const TEST_CLIENT_ID = 'TEST_CLIENT_ID' + +describe('asealBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }); + + describe('isBidRequestValid', () => { + const bid = { + bidder: 'aseal', + params: { + placeUid: '123' + } + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }); + + it('should return false when required param placeUid is not passed', () => { + bid.params = { + placeUid: '' + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }); + + it('should return false when required param placeUid is wrong type', () => { + bid.params = { + placeUid: null + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }); + + describe('buildRequests', () => { + afterEach(() => { + config.resetConfig(); + }); + + it('should return an empty array when there are no bid requests', () => { + const bidRequests = [] + const request = spec.buildRequests(bidRequests) + + expect(request).to.be.an('array').that.is.empty + }); + + it('should send `x-aotter-clientid` header as empty string when user not set config `clientId`', () => { + const bidRequests = [{ + bidder: BIDDER_CODE, + params: { + placeUid: '123' + } + }] + + const bidderRequest = {} + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + + expect(request.options.customHeaders['x-aotter-clientid']).equal('') + }) + + it('should send bid requests to ENDPOINT via POST', () => { + const bidRequests = [{ + bidder: BIDDER_CODE, + params: { + placeUid: '123' + } + }] + + const bidderRequest = { + refererInfo: { + referer: 'https://aseal.in/', + } + } + + config.setConfig({ + aseal: { + clientId: TEST_CLIENT_ID + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + + expect(request.url).to.equal(API_ENDPOINT); + expect(request.method).to.equal('POST') + expect(request.options).deep.equal({ + contentType: 'application/json', + withCredentials: true, + customHeaders: { + 'x-aotter-clientid': TEST_CLIENT_ID, + 'x-aotter-version': HEADER_AOTTER_VERSION, + } + }); + expect(request.data).deep.equal({ + bids: bidRequests, + refererInfo: bidderRequest.refererInfo, + }); + }); + }); + + describe('interpretResponse', () => { + it('should return an empty array when there are no bids', () => { + const serverResponse = {} + const response = spec.interpretResponse(serverResponse); + + expect(response).is.an('array').that.is.empty; + }); + + it('should get correct bid response', () => { + const serverResponse = { + body: [{ + requestId: '2ef08f145b7a4f', + cpm: 3, + width: 300, + height: 250, + creativeId: '123abc', + dealId: '123abc', + currency: 'USD', + netRevenue: false, + mediaType: 'banner', + ttl: 300, + ad: '' + }] + } + const response = spec.interpretResponse(serverResponse); + + expect(response).deep.equal(serverResponse.body); + }); + }) +}); diff --git a/test/spec/modules/asoBidAdapter_spec.js b/test/spec/modules/asoBidAdapter_spec.js new file mode 100644 index 00000000000..0dc779a300d --- /dev/null +++ b/test/spec/modules/asoBidAdapter_spec.js @@ -0,0 +1,340 @@ +import {expect} from 'chai'; +import {spec} from 'modules/asoBidAdapter.js'; +import {parseUrl} from 'src/utils.js'; +import {BANNER, VIDEO} from 'src/mediaTypes.js'; + +describe('Adserver.Online bidding adapter', function () { + const bannerRequest = { + bidder: 'aso', + params: { + zone: 1, + attr: { + keywords: ['a', 'b'], + tags: ['t1', 't2'] + } + }, + adUnitCode: 'adunit-banner', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [240, 400], + ] + } + }, + bidId: 'bidid1', + bidderRequestId: 'bidreq1', + auctionId: 'auctionid1', + userIdAsEids: [{ + source: 'src1', + uids: [ + { + id: 'id123...' + } + ] + }] + }; + + const videoRequest = { + bidder: 'aso', + params: { + zone: 2, + video: { + api: [2], + maxduration: 30 + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]], + protocols: [1, 2], + mimes: ['video/mp4'], + } + }, + adUnitCode: 'adunit-video', + bidId: 'bidid12', + bidderRequestId: 'bidreq2', + auctionId: 'auctionid12' + }; + + const bidderRequest = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'https://example.com' + } + }; + + const gdprConsent = { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + purpose: { + consents: { + 1: true, + 2: true, + 3: false + } + } + } + }; + + const uspConsent = 'usp_consent'; + + describe('isBidRequestValid', function () { + it('should return true when required params found in bidVideo', function () { + expect(spec.isBidRequestValid(videoRequest)).to.be.true + }); + + it('should return true when required params found in bidBanner', function () { + expect(spec.isBidRequestValid(bannerRequest)).to.be.true + }); + + it('should return false when required params not found', function () { + expect(spec.isBidRequestValid({})).to.be.false; + }); + + it('should return false when required params are not passed', function () { + const bid = Object.assign({}, bannerRequest); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.be.false + }); + + it('should return false when required zone param not found', function () { + const bid = JSON.parse(JSON.stringify(videoRequest)); + delete bid.params.zone; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + it('creates a valid banner request', function () { + bannerRequest.getFloor = () => ({ currency: 'USD', floor: 0.5 }); + + const requests = spec.buildRequests([bannerRequest], bidderRequest); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; + + expect(request).to.exist; + expect(request.method).to.equal('POST'); + const parsedRequestUrl = parseUrl(request.url); + expect(parsedRequestUrl.hostname).to.equal('srv.aso1.net'); + expect(parsedRequestUrl.pathname).to.equal('/prebid/bidder'); + + const query = parsedRequestUrl.search; + expect(query.pbjs).to.equal('$prebid.version$'); + expect(query.zid).to.equal('1'); + + expect(request.data).to.exist; + + const payload = request.data; + + expect(payload.site).to.not.equal(null); + expect(payload.site.ref).to.equal(''); + expect(payload.site.page).to.equal('https://example.com'); + + expect(payload.device).to.not.equal(null); + expect(payload.device.w).to.equal(window.innerWidth); + expect(payload.device.h).to.equal(window.innerHeight); + + expect(payload.imp).to.have.lengthOf(1); + + expect(payload.imp[0].tagid).to.equal('adunit-banner'); + expect(payload.imp[0].banner).to.not.equal(null); + expect(payload.imp[0].banner.w).to.equal(300); + expect(payload.imp[0].banner.h).to.equal(250); + expect(payload.imp[0].bidfloor).to.equal(0.5); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); + }); + + it('creates a valid video request', function () { + const requests = spec.buildRequests([videoRequest], bidderRequest); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; + + expect(request).to.exist; + expect(request.method).to.equal('POST'); + const parsedRequestUrl = parseUrl(request.url); + expect(parsedRequestUrl.hostname).to.equal('srv.aso1.net'); + expect(parsedRequestUrl.pathname).to.equal('/prebid/bidder'); + + const query = parsedRequestUrl.search; + expect(query.pbjs).to.equal('$prebid.version$'); + expect(query.zid).to.equal('2'); + + expect(request.data).to.not.be.empty; + + const payload = request.data; + + expect(payload.site).to.not.equal(null); + expect(payload.site.ref).to.equal(''); + expect(payload.site.page).to.equal('https://example.com'); + + expect(payload.device).to.not.equal(null); + expect(payload.device.w).to.equal(window.innerWidth); + expect(payload.device.h).to.equal(window.innerHeight); + + expect(payload.imp).to.have.lengthOf(1); + + expect(payload.imp[0].tagid).to.equal('adunit-video'); + expect(payload.imp[0].video).to.not.equal(null); + expect(payload.imp[0].video.w).to.equal(640); + expect(payload.imp[0].video.h).to.equal(480); + expect(payload.imp[0].banner).to.be.undefined; + }); + }); + + describe('GDPR/USP compliance', function () { + it('should send GDPR/USP consent data if it applies', function () { + bidderRequest.gdprConsent = gdprConsent; + bidderRequest.uspConsent = uspConsent; + + const requests = spec.buildRequests([bannerRequest], bidderRequest); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; + + expect(request.data).to.not.be.empty; + + const payload = request.data; + + expect(payload.user.ext.consent).to.equal('consentString'); + expect(payload.regs.ext.us_privacy).to.equal(uspConsent); + expect(payload.regs.ext.gdpr).to.equal(1); + }); + + it('should not send GDPR/USP consent data if it does not apply', function () { + bidderRequest.gdprConsent = null; + bidderRequest.uspConsent = null; + + const requests = spec.buildRequests([bannerRequest], bidderRequest); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; + + expect(request.data).to.not.be.empty; + + const payload = request.data; + + expect(payload).to.not.have.nested.property('regs.ext.gdpr'); + expect(payload).to.not.have.nested.property('user.ext.consent'); + expect(payload).to.not.have.nested.property('regs.ext.us_privacy'); + }); + }); + + describe('response handler', function () { + const bannerResponse = { + body: { + id: 'auctionid1', + bidid: 'bidid1', + seatbid: [{ + bid: [ + { + impid: 'impid1', + price: 0.3, + crid: 321, + adm: '', + w: 300, + h: 250, + adomain: ['example.com'], + } + ] + }], + cur: 'USD', + ext: { + user_syncs: [ + { + url: 'sync_url', + type: 'iframe' + } + ] + } + }, + }; + + const videoResponse = { + body: { + id: 'auctionid2', + bidid: 'bidid2', + seatbid: [{ + bid: [ + { + impid: 'impid2', + price: 0.5, + crid: 123, + adm: '', + adomain: ['example.com'], + w: 640, + h: 480, + } + ] + }], + cur: 'USD' + }, + }; + + it('handles banner responses', function () { + bannerRequest.bidRequest = { + mediaType: BANNER + }; + const result = spec.interpretResponse(bannerResponse, bannerRequest); + + expect(result).to.have.lengthOf(1); + + expect(result[0]).to.exist; + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].mediaType).to.equal(BANNER); + expect(result[0].creativeId).to.equal(321); + expect(result[0].cpm).to.be.within(0.1, 0.5); + expect(result[0].ad).to.equal(''); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + expect(result[0].dealId).to.not.exist; + expect(result[0].meta.advertiserDomains[0]).to.equal('example.com'); + }); + + it('handles video responses', function () { + const request = { + bidRequest: videoRequest + }; + request.bidRequest.mediaType = VIDEO; + + const result = spec.interpretResponse(videoResponse, request); + expect(result).to.have.lengthOf(1); + + expect(result[0].width).to.equal(640); + expect(result[0].height).to.equal(480); + expect(result[0].mediaType).to.equal(VIDEO); + expect(result[0].creativeId).to.equal(123); + expect(result[0].cpm).to.equal(0.5); + expect(result[0].vastXml).to.equal(''); + expect(result[0].renderer).to.be.a('object'); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + }); + + it('handles empty responses', function () { + const response = []; + const bidderRequest = {}; + + const result = spec.interpretResponse(response, bidderRequest); + expect(result.length).to.equal(0); + }); + + describe('getUserSyncs', function () { + const syncOptions = { + iframeEnabled: true + }; + + it('should return iframe sync option', function () { + expect(spec.getUserSyncs(syncOptions, [bannerResponse], gdprConsent, uspConsent)[0].type).to.equal('iframe'); + expect(spec.getUserSyncs(syncOptions, [bannerResponse], gdprConsent, uspConsent)[0].url).to.equal( + 'sync_url?gdpr=1&consents_str=consentString&consents=1%2C2&us_privacy=usp_consent&' + ); + }); + }); + }); +}); diff --git a/test/spec/modules/atomxBidAdapter_spec.js b/test/spec/modules/atomxBidAdapter_spec.js deleted file mode 100644 index d798bd6308c..00000000000 --- a/test/spec/modules/atomxBidAdapter_spec.js +++ /dev/null @@ -1,119 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/atomxBidAdapter.js'; - -describe('atomxAdapterTest', function () { - describe('bidRequestValidity', function () { - it('bidRequest with id param', function () { - expect(spec.isBidRequestValid({ - bidder: 'atomx', - params: { - id: 1234, - }, - })).to.equal(true); - }); - - it('bidRequest with no id param', function () { - expect(spec.isBidRequestValid({ - bidder: 'atomx', - params: { - }, - })).to.equal(false); - }); - }); - - describe('bidRequest', function () { - const bidRequests = [{ - 'bidder': 'atomx', - 'params': { - 'id': '123' - }, - 'adUnitCode': 'aaa', - 'transactionId': '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', - 'sizes': [300, 250], - 'bidId': '1abgs362e0x48a8', - 'bidderRequestId': '70deaff71c281d', - 'auctionId': '5c66da22-426a-4bac-b153-77360bef5337' - }, - { - 'bidder': 'atomx', - 'params': { - 'id': '456', - }, - 'adUnitCode': 'bbb', - 'transactionId': '193995b4-7122-4739-959b-2463282a138b', - 'sizes': [[800, 600]], - 'bidId': '22aidtbx5eabd9', - 'bidderRequestId': '70deaff71c281d', - 'auctionId': 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' - }]; - - it('bidRequest HTTP method', function () { - const requests = spec.buildRequests(bidRequests); - requests.forEach(function(requestItem) { - expect(requestItem.method).to.equal('GET'); - }); - }); - - it('bidRequest url', function () { - const requests = spec.buildRequests(bidRequests); - requests.forEach(function(requestItem) { - expect(requestItem.url).to.match(new RegExp('p\\.ato\\.mx/placement')); - }); - }); - - it('bidRequest data', function () { - const requests = spec.buildRequests(bidRequests); - expect(requests[0].data.id).to.equal('123'); - expect(requests[0].data.size).to.equal('300x250'); - expect(requests[0].data.prebid).to.equal('1abgs362e0x48a8'); - expect(requests[1].data.id).to.equal('456'); - expect(requests[1].data.size).to.equal('800x600'); - expect(requests[1].data.prebid).to.equal('22aidtbx5eabd9'); - }); - }); - - describe('interpretResponse', function () { - const bidRequest = { - 'method': 'GET', - 'url': 'https://p.ato.mx/placement', - 'data': { - 'v': 12, - 'id': '123', - 'size': '300x250', - 'prebid': '22aidtbx5eabd9', - 'b': 0, - 'h': '7t3y9', - 'type': 'javascript', - 'screen': '800x600x32', - 'timezone': 0, - 'domain': 'https://example.com', - 'r': '', - } - }; - - const bidResponse = { - body: { - 'cpm': 0.00009, - 'width': 300, - 'height': 250, - 'url': 'https://atomx.com', - 'creative_id': 456, - 'code': '22aidtbx5eabd9', - }, - headers: {} - }; - - it('result is correct', function () { - const result = spec.interpretResponse(bidResponse, bidRequest); - - expect(result[0].requestId).to.equal('22aidtbx5eabd9'); - expect(result[0].cpm).to.equal(0.00009 * 1000); - expect(result[0].width).to.equal(300); - expect(result[0].height).to.equal(250); - expect(result[0].creativeId).to.equal(456); - expect(result[0].currency).to.equal('USD'); - expect(result[0].ttl).to.equal(60); - expect(result[0].adUrl).to.equal('https://atomx.com'); - }); - }); -}); diff --git a/test/spec/modules/atsAnalyticsAdapter_spec.js b/test/spec/modules/atsAnalyticsAdapter_spec.js index 59b9105925a..cae90a19223 100644 --- a/test/spec/modules/atsAnalyticsAdapter_spec.js +++ b/test/spec/modules/atsAnalyticsAdapter_spec.js @@ -5,33 +5,44 @@ import {server} from '../../mocks/xhr.js'; import {parseBrowser} from '../../../modules/atsAnalyticsAdapter.js'; import {getStorageManager} from '../../../src/storageManager.js'; import {analyticsUrl} from '../../../modules/atsAnalyticsAdapter.js'; +let utils = require('src/utils'); let events = require('src/events'); let constants = require('src/constants.json'); export const storage = getStorageManager(); +let sandbox; +let clock; +let now = new Date(); describe('ats analytics adapter', function () { beforeEach(function () { sinon.stub(events, 'getEvents').returns([]); storage.setCookie('_lr_env_src_ats', 'true', 'Thu, 01 Jan 1970 00:00:01 GMT'); + sandbox = sinon.sandbox.create(); + clock = sandbox.useFakeTimers(now.getTime()); }); afterEach(function () { events.getEvents.restore(); atsAnalyticsAdapter.getUserAgent.restore(); atsAnalyticsAdapter.disableAnalytics(); + Math.random.restore(); + sandbox.restore(); + clock.restore(); }); describe('track', function () { it('builds and sends request and response data', function () { - sinon.stub(atsAnalyticsAdapter, 'shouldFireRequest').returns(true); + sinon.stub(Math, 'random').returns(0.99); sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); - let now = new Date(); + now.setTime(now.getTime() + 3600000); storage.setCookie('_lr_env_src_ats', 'true', now.toUTCString()); storage.setCookie('_lr_sampling_rate', '10', now.toUTCString()); + this.timeout(2100); + let initOptions = { pid: '10433394' }; @@ -57,7 +68,7 @@ describe('ats analytics adapter', function () { 'refererInfo': { 'referer': 'https://example.com/dev' }, - 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' }; // prepare general auction - response let bidResponse = { @@ -85,7 +96,7 @@ describe('ats analytics adapter', function () { let expectedAfterBid = { 'Data': [{ 'has_envelope': true, - 'adapter_version': 1, + 'adapter_version': 3, 'bidder': 'appnexus', 'bid_id': '30c77d079cdf17', 'auction_id': 'a5b849e5-87d7-4205-8300-d063084fcfb7', @@ -98,10 +109,30 @@ describe('ats analytics adapter', function () { 'response_time_stamp': '2020-02-03T14:23:11.978Z', 'currency': 'USD', 'cpm': 0.5, - 'net_revenue': true + 'net_revenue': true, + 'bid_won': true }] }; + let wonRequest = { + 'adId': '2eddfdc0c791dc', + 'mediaType': 'banner', + 'requestId': '30c77d079cdf17', + 'cpm': 0.5, + 'creativeId': 29681110, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + 'statusMessage': 'Bid available', + 'responseTimestamp': 1633525319061, + 'requestTimestamp': 1633525319258, + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'size': '300x250', + 'status': 'rendered' + }; + // lets simulate that some bidders timeout let bidTimeoutArgsV1 = [ { @@ -143,6 +174,14 @@ describe('ats analytics adapter', function () { // Step 5: Send auction end event events.emit(constants.EVENTS.AUCTION_END, {}); + // Step 6: Send bid won event + events.emit(constants.EVENTS.BID_WON, wonRequest); + + sandbox.stub($$PREBID_GLOBAL$$, 'getAllWinningBids').callsFake((key) => { + return [wonRequest] + }); + + clock.tick(2000); let requests = server.requests.filter(req => { return req.url.indexOf(analyticsUrl) > -1; @@ -151,7 +190,8 @@ describe('ats analytics adapter', function () { expect(requests.length).to.equal(1); let realAfterBid = JSON.parse(requests[0].requestBody); - // Step 6: assert real data after bid and expected data + + // Step 7: assert real data after bid and expected data expect(realAfterBid['Data']).to.deep.equal(expectedAfterBid['Data']); // check that the publisher ID is configured via options @@ -159,23 +199,79 @@ describe('ats analytics adapter', function () { }) it('check browser is safari', function () { sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); + sinon.stub(Math, 'random').returns(0.99); let browser = parseBrowser(); expect(browser).to.equal('Safari'); }) it('check browser is chrome', function () { sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/80.0.3987.95 Mobile/15E148 Safari/604.1'); + sinon.stub(Math, 'random').returns(0.99); let browser = parseBrowser(); expect(browser).to.equal('Chrome'); }) it('check browser is edge', function () { sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43'); + sinon.stub(Math, 'random').returns(0.99); let browser = parseBrowser(); expect(browser).to.equal('Microsoft Edge'); }) it('check browser is firefox', function () { sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (iPhone; CPU OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/23.0 Mobile/15E148 Safari/605.1.15'); + sinon.stub(Math, 'random').returns(0.99); let browser = parseBrowser(); expect(browser).to.equal('Firefox'); }) + it('check browser is unknown', function () { + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns(undefined); + sinon.stub(Math, 'random').returns(0.99); + let browser = parseBrowser(); + expect(browser).to.equal('Unknown'); + }) + it('should not fire analytics request if sampling rate is 0', function () { + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); + sinon.stub(Math, 'random').returns(0.99); + let result = atsAnalyticsAdapter.shouldFireRequest(0); + expect(result).to.equal(false); + }) + it('should fire analytics request', function () { + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); + sinon.stub(Math, 'random').returns(0.99); + // publisher can try to pass anything they want but we will set sampling rate to 100, which means we will have 1% of requests + let result = atsAnalyticsAdapter.shouldFireRequest(8); + expect(result).to.equal(true); + }) + it('should not fire analytics request if math random is something other then 0.99', function () { + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); + sinon.stub(Math, 'random').returns(0.98); + // publisher can try to pass anything they want but we will set sampling rate to 100, which means we will have 1% of requests + let result = atsAnalyticsAdapter.shouldFireRequest(10); + expect(result).to.equal(false); + }) + + it('should set cookie value to 10 for _lr_sampling_rate', function () { + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); + sinon.stub(Math, 'random').returns(0.99); + atsAnalyticsAdapter.setSamplingCookie(10); + let samplingRate = storage.getCookie('_lr_sampling_rate'); + expect(samplingRate).to.equal('10'); + }) + + it('should set cookie value to 0 for _lr_sampling_rate', function () { + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); + sinon.stub(Math, 'random').returns(0.99); + atsAnalyticsAdapter.setSamplingCookie(0); + let samplingRate = storage.getCookie('_lr_sampling_rate'); + expect(samplingRate).to.equal('0'); + }) + + it('enable analytics', function () { + sandbox.stub(utils, 'logError'); + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); + sinon.stub(Math, 'random').returns(0.99); + atsAnalyticsAdapter.enableAnalytics({ + options: {} + }); + expect(utils.logError.called).to.equal(true); + }) }) }) diff --git a/test/spec/modules/audiencerunBidAdapter_spec.js b/test/spec/modules/audiencerunBidAdapter_spec.js index 826944abaf5..7c1279f5073 100644 --- a/test/spec/modules/audiencerunBidAdapter_spec.js +++ b/test/spec/modules/audiencerunBidAdapter_spec.js @@ -10,7 +10,6 @@ const BID_SERVER_RESPONSE = { { 'bidId': '51ef8751f9aead', 'zoneId': '12345abcde', - 'adId': '1234', 'crid': '5678', 'cpm': 8.021951999999999999, 'currency': 'USD', @@ -19,7 +18,8 @@ const BID_SERVER_RESPONSE = { 'isNet': false, 'buying_type': 'rtb', 'syncUrl': 'https://ac.audiencerun.com/f/sync.html', - 'adm': '' + 'adm': '', + 'adomain': ['example.com'] } ] } @@ -77,7 +77,7 @@ describe('AudienceRun bid adapter tests', function() { }); describe('buildRequests', function() { - let bidRequests = [ + const bidRequests = [ { 'bidder': 'audiencerun', 'bidId': '51ef8751f9aead', @@ -96,6 +96,7 @@ describe('AudienceRun bid adapter tests', function() { 'bidRequestsCount': 1 } ]; + const bidRequest = bidRequests[0]; it('sends a valid bid request to ENDPOINT via POST', function() { const request = spec.buildRequests(bidRequests, { @@ -156,12 +157,43 @@ describe('AudienceRun bid adapter tests', function() { expect(payload2.gdpr.consent).to.equal(consentString); expect(payload2.gdpr.applies).to.equal(false); }); + + it('should use a bidfloor with a 0 value', function() { + const bid = Object.assign({}, bidRequest); + const request = spec.buildRequests([bid]); + const payload = JSON.parse(request.data); + expect(payload.bids[0].bidfloor).to.exist.and.to.equal(0); + }) + + it('should use bidfloor param value', function () { + const bid = Object.assign({}, bidRequest, { + params: { + 'bidfloor': 0.2 + } + }) + const request = spec.buildRequests([bid]); + const payload = JSON.parse(request.data); + expect(payload.bids[0].bidfloor).to.exist.and.to.equal(0.2); + }); + + it('should use floors module value', function () { + const bid = Object.assign({}, bidRequest, { + params: { + 'bidfloor': 0.5 + } + }) + bid.getFloor = () => { + return { floor: 1, currency: 'USD' } + } + const request = spec.buildRequests([bid]); + const payload = JSON.parse(request.data); + expect(payload.bids[0].bidfloor).to.exist.and.to.equal(1); + }); }); describe('interpretResponse', function () { const expectedResponse = [{ 'requestId': '51ef8751f9aead', - 'adId': '12345abcde', 'cpm': 8.021951999999999999, 'width': '728', 'height': '90', @@ -170,7 +202,10 @@ describe('AudienceRun bid adapter tests', function() { 'netRevenue': false, 'ttl': 300, 'ad': '', - 'mediaType': 'banner' + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['example.com'] + } }]; it('should get the correct bid response by display ad', function () { @@ -178,7 +213,7 @@ describe('AudienceRun bid adapter tests', function() { expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('handles empty bid response', function () { + it('should handle empty bid response', function () { const response = { body: {} }; @@ -201,4 +236,10 @@ describe('AudienceRun bid adapter tests', function() { expect(syncs).to.deep.equal([{type: 'iframe', url: 'https://ac.audiencerun.com/f/sync.html'}]) }); }); + + describe('onTimeout', function () { + it('should exists and be a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + }); }); diff --git a/test/spec/modules/automatadBidAdapter_spec.js b/test/spec/modules/automatadBidAdapter_spec.js index 9d828bad4c3..4a15209822b 100644 --- a/test/spec/modules/automatadBidAdapter_spec.js +++ b/test/spec/modules/automatadBidAdapter_spec.js @@ -30,6 +30,7 @@ describe('automatadBidAdapter', function () { { 'bid': [ { + 'bidId': '123', 'adm': '', 'adomain': [ 'someAdDomain' @@ -173,14 +174,29 @@ describe('automatadBidAdapter', function () { }) }) - describe('getUserSyncs', function () { - it('should return iframe sync', function () { - let sync = spec.getUserSyncs() - expect(sync.length).to.equal(1) - expect(sync[0].type === 'iframe') - expect(typeof sync[0].url === 'string') + describe('onTimeout', function () { + const timeoutData = { + 'bidId': '123', + 'bidder': 'automatad', + 'adUnitCode': 'div-13', + 'auctionId': '1232', + 'params': [ + { + 'siteId': 'test', + 'placementId': 'test123' + } + ], + 'timeout': 1000 + } + + it('should exists and be a function', function () { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + + it('should include timeoutData', function () { + expect(spec.onTimeout(timeoutData)).to.be.undefined; }) - }) + }); describe('onBidWon', function () { let serverResponses = spec.interpretResponse(expectedResponse[0]) diff --git a/test/spec/modules/avocetBidAdapter_spec.js b/test/spec/modules/avocetBidAdapter_spec.js deleted file mode 100644 index 2a2f29e48d2..00000000000 --- a/test/spec/modules/avocetBidAdapter_spec.js +++ /dev/null @@ -1,167 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/avocetBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; -import { config } from 'src/config'; - -describe('Avocet adapter', function () { - beforeEach(function () { - config.setConfig({ - currency: { - adServerCurrency: 'USD', - }, - publisherDomain: 'test.com', - fpd: { - some: 'data', - }, - }); - }); - - afterEach(function () { - config.resetConfig(); - }); - - describe('inherited functions', function () { - it('exists and is a function', function () { - const adapter = newBidder(spec); - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - it('should return false for bid request missing params', () => { - const invalidBidRequest = { - bid: {}, - }; - expect(spec.isBidRequestValid(invalidBidRequest)).to.equal(false); - }); - it('should return false for an invalid type placement param', () => { - const invalidBidRequest = { - params: { - placement: 123, - }, - }; - expect(spec.isBidRequestValid(invalidBidRequest)).to.equal(false); - }); - it('should return false for an invalid length placement param', () => { - const invalidBidRequest = { - params: { - placement: '123', - }, - }; - expect(spec.isBidRequestValid(invalidBidRequest)).to.equal(false); - }); - it('should return true for a valid length placement param', () => { - const validBidRequest = { - params: { - placement: '012345678901234567890123', - }, - }; - expect(spec.isBidRequestValid(validBidRequest)).to.equal(true); - }); - }); - describe('buildRequests', function () { - it('constructs a valid POST request', function () { - const request = spec.buildRequests( - [ - { - bidder: 'avct', - params: { - placement: '012345678901234567890123', - }, - userId: { - id5id: { - uid: 'test' - } - } - }, - { - bidder: 'avct', - params: { - placement: '012345678901234567890123', - }, - }, - ], - exampleBidderRequest - ); - expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://ads.avct.cloud/prebid'); - - const requestData = JSON.parse(request.data); - expect(requestData.ext).to.be.an('object'); - expect(requestData.ext.currency).to.equal('USD'); - expect(requestData.ext.publisherDomain).to.equal('test.com'); - expect(requestData.ext.fpd).to.deep.equal({ some: 'data' }); - expect(requestData.ext.schain).to.deep.equal({ - validation: 'strict', - config: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'indirectseller.com', - sid: '00001', - hp: 1, - }, - ], - }, - }); - expect(requestData.ext.id5id).to.equal('test'); - expect(requestData.bids).to.be.an('array'); - expect(requestData.bids.length).to.equal(2); - }); - }); - describe('interpretResponse', function () { - it('no response', function () { - const response = spec.interpretResponse(); - expect(response).to.be.an('array'); - expect(response.length).to.equal(0); - }); - it('no body', function () { - const response = spec.interpretResponse({}); - expect(response).to.be.an('array'); - expect(response.length).to.equal(0); - }); - it('null body', function () { - const response = spec.interpretResponse({ body: null }); - expect(response).to.be.an('array'); - expect(response.length).to.equal(0); - }); - it('empty body', function () { - const response = spec.interpretResponse({ body: {} }); - expect(response).to.be.an('array'); - expect(response.length).to.equal(0); - }); - it('null body.responses', function () { - const response = spec.interpretResponse({ body: { responses: null } }); - expect(response).to.be.an('array'); - expect(response.length).to.equal(0); - }); - it('array body', function () { - const response = spec.interpretResponse({ body: [{}] }); - expect(response).to.be.an('array'); - expect(response.length).to.equal(1); - }); - it('array body.responses', function () { - const response = spec.interpretResponse({ body: { responses: [{}] } }); - expect(response).to.be.an('array'); - expect(response.length).to.equal(1); - }); - }); -}); - -const exampleBidderRequest = { - schain: { - validation: 'strict', - config: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'indirectseller.com', - sid: '00001', - hp: 1, - }, - ], - }, - }, -}; diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index c7ae5c799ac..d9b8cac10b4 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec, VIDEO_ENDPOINT, BANNER_ENDPOINT, OUTSTREAM_SRC, DEFAULT_MIMES } from 'modules/beachfrontBidAdapter.js'; -import { parseUrl } from 'src/utils.js'; +import { config } from 'src/config.js'; +import { parseUrl, deepAccess } from 'src/utils.js'; describe('BeachfrontAdapter', function () { let bidRequests; @@ -171,6 +172,24 @@ describe('BeachfrontAdapter', function () { expect(data.cur).to.deep.equal(['USD']); }); + it('must read from the floors module if available', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + bidRequest.getFloor = () => ({ currency: 'USD', floor: 1.16 }); + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].bidfloor).to.equal(1.16); + }); + + it('must use the bid floor param if no value is returned from the floors module', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + bidRequest.getFloor = () => ({}); + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); + }); + it('must parse bid size from a nested array', function () { const width = 640; const height = 480; @@ -298,40 +317,54 @@ describe('BeachfrontAdapter', function () { expect(data.source.ext.schain).to.deep.equal(schain); }); - it('must add the Trade Desk User ID to the request', () => { - const tdid = '4321'; - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { video: {} }; - bidRequest.userId = { tdid }; - const requests = spec.buildRequests([ bidRequest ]); - const data = requests[0].data; - expect(data.user.ext.eids[0]).to.deep.equal({ - source: 'adserver.org', - uids: [{ - id: tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }); - }); - - it('must add the IdentityLink ID to the request', () => { - const idl_env = '4321'; + it('must add supported user IDs to the request', () => { + const userId = { + tdid: '54017816', + idl_env: '13024996', + uid2: { id: '45843401' }, + haloId: { haloId: '60314917', auSeg: ['segment1', 'segment2'] } + }; const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: {} }; - bidRequest.userId = { idl_env }; + bidRequest.userId = userId; const requests = spec.buildRequests([ bidRequest ]); const data = requests[0].data; - expect(data.user.ext.eids[0]).to.deep.equal({ - source: 'liveramp.com', - uids: [{ - id: idl_env, - ext: { - rtiPartner: 'idl' - } - }] - }); + expect(data.user.ext.eids).to.deep.equal([ + { + source: 'adserver.org', + uids: [{ + id: userId.tdid, + ext: { + rtiPartner: 'TDID' + } + }] + }, + { + source: 'liveramp.com', + uids: [{ + id: userId.idl_env, + ext: { + rtiPartner: 'idl' + } + }] + }, + { + source: 'uidapi.com', + uids: [{ + id: userId.uid2.id, + ext: { + rtiPartner: 'UID2' + } + }] + }, + { + source: 'audigent.com', + uids: [{ + id: userId.haloId, + atype: 1, + }] + } + ]); }); }); @@ -385,6 +418,24 @@ describe('BeachfrontAdapter', function () { expect(data.ua).to.equal(navigator.userAgent); }); + it('must read from the floors module if available', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + bidRequest.getFloor = () => ({ currency: 'USD', floor: 1.16 }); + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.slots[0].bidfloor).to.equal(1.16); + }); + + it('must use the bid floor param if no value is returned from the floors module', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + bidRequest.getFloor = () => ({}); + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.slots[0].bidfloor).to.equal(bidRequest.params.bidfloor); + }); + it('must parse bid size from a nested array', function () { const width = 300; const height = 250; @@ -487,24 +538,85 @@ describe('BeachfrontAdapter', function () { expect(data.schain).to.deep.equal(schain); }); - it('must add the Trade Desk User ID to the request', () => { - const tdid = '4321'; + it('must add supported user IDs to the request', () => { + const userId = { + tdid: '54017816', + idl_env: '13024996', + uid2: { id: '45843401' }, + haloId: { haloId: '60314917', auSeg: ['segment1', 'segment2'] } + }; const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { banner: {} }; - bidRequest.userId = { tdid }; + bidRequest.userId = userId; const requests = spec.buildRequests([ bidRequest ]); const data = requests[0].data; - expect(data.tdid).to.equal(tdid); + expect(data.tdid).to.equal(userId.tdid); + expect(data.idl).to.equal(userId.idl_env); + expect(data.uid2).to.equal(userId.uid2.id); + expect(data.haloid).to.equal(userId.haloId); + }); + }); + + describe('with first-party data', function () { + let sandbox + + beforeEach(function () { + sandbox = sinon.sandbox.create(); }); - it('must add the IdentityLink ID to the request', () => { - const idl_env = '4321'; + afterEach(function () { + sandbox.restore(); + }); + + it('must add first-party data to the video bid request', function () { + sandbox.stub(config, 'getConfig').callsFake(key => { + const cfg = { + ortb2: { + site: { + keywords: 'test keyword' + }, + user: { + data: 'some user data' + } + } + }; + return deepAccess(cfg, key); + }); + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html' + } + }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.user.data).to.equal('some user data'); + expect(data.site.keywords).to.equal('test keyword'); + expect(data.site.page).to.equal('http://example.com/page.html'); + expect(data.site.domain).to.equal('example.com'); + }); + + it('must add first-party data to the banner bid request', function () { + sandbox.stub(config, 'getConfig').callsFake(key => { + const cfg = { + ortb2: { + site: { + keywords: 'test keyword' + }, + user: { + data: 'some user data' + } + } + }; + return deepAccess(cfg, key); + }); const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { banner: {} }; - bidRequest.userId = { idl_env }; const requests = spec.buildRequests([ bidRequest ]); const data = requests[0].data; - expect(data.idl).to.equal(idl_env); + expect(data.ortb2.user.data).to.equal('some user data'); + expect(data.ortb2.site.keywords).to.equal('test keyword'); }); }); @@ -609,6 +721,7 @@ describe('BeachfrontAdapter', function () { width: width, height: height, renderer: null, + meta: { mediaType: 'video', advertiserDomains: [] }, mediaType: 'video', currency: 'USD', netRevenue: true, @@ -616,26 +729,6 @@ describe('BeachfrontAdapter', function () { }); }); - it('should default to the legacy "cmpId" value for the creative ID', () => { - const width = 640; - const height = 480; - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { - video: { - playerSize: [ width, height ] - } - }; - const serverResponse = { - bidPrice: 5.00, - url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', - cmpId: '123abc' - }; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - expect(bidResponse).to.deep.contain({ - creativeId: serverResponse.cmpId - }); - }); - it('should return only vast url if the response type is "nurl"', () => { const width = 640; const height = 480; @@ -697,6 +790,31 @@ describe('BeachfrontAdapter', function () { }); expect(bidResponse.renderer.render).to.be.a('function'); }); + + it('should return meta data for the bid response', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + video: { + playerSize: [ width, height ] + } + }; + const serverResponse = { + bidPrice: 5.00, + url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', + meta: { + advertiserDomains: ['example.com'], + advertiserId: '123' + } + }; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.meta).to.deep.equal({ + mediaType: 'video', + advertiserDomains: ['example.com'], + advertiserId: '123' + }); + }); }); describe('for banner bids', function () { @@ -751,6 +869,7 @@ describe('BeachfrontAdapter', function () { cpm: serverResponse[ i ].price, width: serverResponse[ i ].w, height: serverResponse[ i ].h, + meta: { mediaType: 'banner', advertiserDomains: [] }, mediaType: 'banner', currency: 'USD', netRevenue: true, @@ -758,6 +877,32 @@ describe('BeachfrontAdapter', function () { }); } }); + + it('should return meta data for the bid response', () => { + bidRequests[0].mediaTypes = { + banner: { + sizes: [[ 300, 250 ], [ 728, 90 ]] + } + }; + const serverResponse = [{ + slot: bidRequests[0].adUnitCode, + adm: '
', + crid: 'crid_1', + price: 3.02, + w: 728, + h: 90, + meta: { + advertiserDomains: ['example.com'], + advertiserId: '123' + } + }]; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest: bidRequests }); + expect(bidResponse[0].meta).to.deep.equal({ + mediaType: 'banner', + advertiserDomains: ['example.com'], + advertiserId: '123' + }); + }); }); }); diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js new file mode 100644 index 00000000000..832ad2707d3 --- /dev/null +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -0,0 +1,195 @@ +import { expect } from 'chai'; +import { spec } from 'modules/beopBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +const utils = require('src/utils'); + +const ENDPOINT = 'https://hb.beop.io/bid'; + +let validBid = { + 'bidder': 'beop', + 'params': { + 'accountId': '5a8af500c9e77c00017e4cad' + }, + 'adUnitCode': 'bellow-article', + 'mediaTypes': { + 'banner': { + 'sizes': [[1, 1]] + } + }, + 'getFloor': () => { + return { + currency: 'USD', + floor: 10, + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843', + 'creativeId': 'er2ee' +}; + +describe('BeOp Bid Adapter tests', () => { + afterEach(function () { + config.setConfig({}); + }); + + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function() { + it('should return true when accountId params found', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return true if no accountId but networkId', function () { + let bid = Object.assign({}, validBid); + delete bid.params; + bid.params = { + 'networkId': '5a8af500c9e77c00017e4aaa' + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if neither account or network id param found', function () { + let bid = Object.assign({}, validBid); + delete bid.params; + bid.params = { + 'someId': '5a8af500c9e77c00017e4aaa' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if account Id param is not an ObjectId', function () { + let bid = Object.assign({}, validBid); + delete bid.params; + bid.params = { + 'someId': '12345' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if there is no banner media type', function () { + let bid = Object.assign({}, validBid); + delete bid.mediaTypes; + bid.mediaTypes = { + 'native': { + 'sizes': [[1, 1]] + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = []; + bidRequests.push(validBid); + + it('should build the request', function () { + config.setConfig({'currency': {'adServerCurrency': 'USD'}}); + const request = spec.buildRequests(bidRequests, {}); + const payload = JSON.parse(request.data); + const url = request.url; + expect(url).to.equal(ENDPOINT); + expect(payload.pid).to.exist; + expect(payload.pid).to.equal('5a8af500c9e77c00017e4cad'); + expect(payload.slts[0].name).to.exist; + expect(payload.slts[0].name).to.equal('bellow-article'); + expect(payload.slts[0].flr).to.equal(10); + }); + + it('should call the endpoint with GDPR consent and pageURL info if found', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = + { + 'gdprConsent': + { + 'gdprApplies': true, + 'consentString': consentString + }, + 'refererInfo': + { + 'canonicalUrl': 'http://test.te' + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.tc_string).to.exist; + expect(payload.tc_string).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='); + expect(payload.url).to.exist; + expect(payload.url).to.equal('http://localhost:9876/context.html'); + }); + }); + + describe('interpretResponse', function() { + let serverResponse = { + 'body': { + 'bids': [ + { + 'requestId': 'aaaa', + 'cpm': 1.0, + 'currency': 'EUR', + 'creativeId': '60f691be1515670a2a09aea2', + 'netRevenue': true, + 'width': 1, + 'height': 1, + 'ad': '
', + 'meta': { + 'advertiserId': '60f691be1515670a2a09aea1' + } + } + ] + } + } + it('should interpret the response by pushing it in the bids elem', function () { + const response = spec.interpretResponse(serverResponse, validBid); + + expect(response[0].ad).to.exist; + expect(response[0].requestId).to.exist; + expect(response[0].requestId).to.equal('aaaa'); + }); + }); + + describe('timeout and bid won pixel trigger', function () { + let triggerPixelStub; + + beforeEach(function () { + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('should call triggerPixel utils function when timed out is filled', function () { + spec.onTimeout({}); + spec.onTimeout(); + expect(triggerPixelStub.getCall(0)).to.be.null; + spec.onTimeout({params: {accountId: '5a8af500c9e77c00017e4cad'}, timeout: 2000}); + expect(triggerPixelStub.getCall(0)).to.not.be.null; + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.beop.io'); + expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ca=bid'); + expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ac=timeout'); + expect(triggerPixelStub.getCall(0).args[0]).to.include('pid=5a8af500c9e77c00017e4cad'); + }); + + it('should call triggerPixel utils function on bid won', function () { + spec.onBidWon({}); + spec.onBidWon(); + expect(triggerPixelStub.getCall(0)).to.be.null; + spec.onBidWon({params: {accountId: '5a8af500c9e77c00017e4cad'}, cpm: 1.2}); + expect(triggerPixelStub.getCall(0)).to.not.be.null; + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.beop.io'); + expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ca=bid'); + expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ac=won'); + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('pid=5a8af500c9e77c00017e4cad'); + }); + }); +}); diff --git a/test/spec/modules/betweenBidAdapter_spec.js b/test/spec/modules/betweenBidAdapter_spec.js index 1b1ccd9efd2..65c200748e4 100644 --- a/test/spec/modules/betweenBidAdapter_spec.js +++ b/test/spec/modules/betweenBidAdapter_spec.js @@ -23,6 +23,32 @@ describe('betweenBidAdapterTests', function () { let req_data = JSON.parse(request.data)[0].data; expect(req_data.bidid).to.equal('bid1234'); }); + + it('validate_video_params', function () { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'between', + params: {w: 240, h: 400, s: 1112}, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [970, 250], + maxd: 123, + mind: 234, + codeType: 'unknown code type' + } + }, + }]; + let request = spec.buildRequests(bidRequestData); + let req_data = JSON.parse(request.data)[0].data; + + expect(req_data.mediaType).to.equal(2); + expect(req_data.maxd).to.equal(123); + expect(req_data.mind).to.equal(234); + expect(req_data.pos).to.equal('atf'); + expect(req_data.codeType).to.equal('inpage'); + }); + it('validate itu param', function() { let bidRequestData = [{ bidId: 'bid1234', @@ -77,6 +103,54 @@ describe('betweenBidAdapterTests', function () { expect(req_data.subid).to.equal(1138); }); + + it('validate eids parameter', function() { + const USER_ID_DATA = [ + { + source: 'admixer.net', + uids: [ + { id: '5706411dc1c54268ac2ed668b27f92a3', atype: 3 } + ] + } + ]; + + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'between', + params: { + w: 240, + h: 400, + s: 1112, + }, + sizes: [[240, 400]], + userIdAsEids: USER_ID_DATA, + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = JSON.parse(request.data)[0].data; + + expect(req_data.eids).to.have.deep.members(USER_ID_DATA); + }); + + it('validate eids parameter, if userIdAsEids = undefined', function() { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'between', + params: { + w: 240, + h: 400, + s: 1112, + }, + sizes: [[240, 400]], + userIdAsEids: undefined + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = JSON.parse(request.data)[0].data; + + expect(req_data.eids).to.have.deep.members([]); + }); + it('validate click3rd param', function() { let bidRequestData = [{ bidId: 'bid1234', @@ -182,6 +256,21 @@ describe('betweenBidAdapterTests', function () { expect(bid.requestId).to.equal('bid1234'); expect(bid.ad).to.equal('Ad html'); }); + + it('validate_response_video_params', function () { + let serverResponse = { + body: [{ + mediaType: 2, + vastXml: 'vastXml', + }] + }; + let bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + let bid = bids[0]; + expect(bid.mediaType).to.equal(2); + expect(bid.vastXml).to.equal('vastXml'); + }); + it('validate response params without currency', function () { let serverResponse = { body: [{ @@ -222,49 +311,37 @@ describe('betweenBidAdapterTests', function () { expect(req_data.sizes).to.deep.equal(['970x250', '240x400', '728x90']) }); - it('check sharedId with id and third', function() { - const bidRequestData = [{ - bidId: 'bid123', - bidder: 'between', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - s: 1112, - }, - userId: { - sharedid: { - id: '01EXQE7JKNDRDDVATB0S2GX1NT', - third: '01EXQE7JKNDRDDVATB0S2GX1NT' - } - } - }]; - const shid = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid; - const shid3 = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid3; - expect(shid).to.equal('01EXQE7JKNDRDDVATB0S2GX1NT') && expect(shid3).to.equal('01EXQE7JKNDRDDVATB0S2GX1NT'); + it('check adomain', function() { + const serverResponse = { + body: [{ + bidid: 'bid1234', + cpm: 1.12, + w: 240, + h: 400, + currency: 'USD', + ad: 'Ad html', + adomain: ['domain1.com', 'domain2.com'] + }] + }; + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.meta.advertiserDomains).to.deep.equal(['domain1.com', 'domain2.com']); }); - it('check sharedId with only id', function() { - const bidRequestData = [{ - bidId: 'bid123', - bidder: 'between', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - s: 1112, - }, - userId: { - sharedid: { - id: '01EXQE7JKNDRDDVATB0S2GX1NT', - } - } - }]; - const shid = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid; - const shid3 = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid3; - expect(shid).to.equal('01EXQE7JKNDRDDVATB0S2GX1NT') && expect(shid3).to.equal(''); + it('check server response without adomain', function() { + const serverResponse = { + body: [{ + bidid: 'bid1234', + cpm: 1.12, + w: 240, + h: 400, + currency: 'USD', + ad: 'Ad html', + }] + }; + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.meta.advertiserDomains).to.deep.equal([]); }); }); diff --git a/test/spec/modules/bidViewabilityIO_spec.js b/test/spec/modules/bidViewabilityIO_spec.js new file mode 100644 index 00000000000..5b4944082bc --- /dev/null +++ b/test/spec/modules/bidViewabilityIO_spec.js @@ -0,0 +1,145 @@ +import * as bidViewabilityIO from 'modules/bidViewabilityIO.js'; +import * as events from 'src/events.js'; +import * as utils from 'src/utils.js'; +import * as sinon from 'sinon'; +import { expect } from 'chai'; +import CONSTANTS from 'src/constants.json'; + +describe('#bidViewabilityIO', function() { + const makeElement = (id) => { + const el = document.createElement('div'); + el.setAttribute('id', id); + return el; + } + const banner_bid = { + adUnitCode: 'banner_id', + mediaType: 'banner', + width: 728, + height: 90 + }; + + const large_banner_bid = { + adUnitCode: 'large_banner_id', + mediaType: 'banner', + width: 970, + height: 250 + }; + + const video_bid = { + mediaType: 'video', + }; + + const native_bid = { + mediaType: 'native', + }; + + it('init to be a function', function() { + expect(bidViewabilityIO.init).to.be.a('function') + }); + + describe('isSupportedMediaType tests', function() { + it('banner to be supported', function() { + expect(bidViewabilityIO.isSupportedMediaType(banner_bid)).to.be.true + }); + + it('video not to be supported', function() { + expect(bidViewabilityIO.isSupportedMediaType(video_bid)).to.be.false + }); + + it('native not to be supported', function() { + expect(bidViewabilityIO.isSupportedMediaType(native_bid)).to.be.false + }); + }) + + describe('getViewableOptions tests', function() { + it('normal banner has expected threshold in options object', function() { + expect(bidViewabilityIO.getViewableOptions(banner_bid).threshold).to.equal(bidViewabilityIO.IAB_VIEWABLE_DISPLAY_THRESHOLD); + }); + + it('large banner has expected threshold in options object', function() { + expect(bidViewabilityIO.getViewableOptions(large_banner_bid).threshold).to.equal(bidViewabilityIO.IAB_VIEWABLE_DISPLAY_LARGE_THRESHOLD) + }); + + it('video bid has undefined viewable options', function() { + expect(bidViewabilityIO.getViewableOptions(video_bid)).to.be.undefined + }); + + it('native bid has undefined viewable options', function() { + expect(bidViewabilityIO.getViewableOptions(native_bid)).to.be.undefined + }); + }) + + describe('markViewed tests', function() { + let sandbox; + const mockObserver = { + unobserve: sinon.spy() + }; + const mockEntry = { + target: makeElement('target_id') + }; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + }) + + afterEach(function() { + sandbox.restore() + }) + + it('markViewed returns a function', function() { + expect(bidViewabilityIO.markViewed(banner_bid, mockEntry, mockObserver)).to.be.a('function') + }); + + it('markViewed unobserves', function() { + const emitSpy = sandbox.spy(events, ['emit']); + const func = bidViewabilityIO.markViewed(banner_bid, mockEntry, mockObserver); + func(); + expect(mockObserver.unobserve.calledOnce).to.be.true; + expect(emitSpy.calledOnce).to.be.true; + // expect(emitSpy.firstCall.args).to.be.false; + expect(emitSpy.firstCall.args[0]).to.eq(CONSTANTS.EVENTS.BID_VIEWABLE); + }); + }) + + describe('viewCallbackFactory tests', function() { + let sandbox; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + }) + + afterEach(function() { + sandbox.restore() + }) + + it('viewCallbackFactory returns a function', function() { + expect(bidViewabilityIO.viewCallbackFactory(banner_bid)).to.be.a('function') + }); + + it('viewCallbackFactory function does stuff', function() { + const logMessageSpy = sandbox.spy(utils, ['logMessage']); + const mockObserver = { + unobserve: sandbox.spy() + }; + const mockEntries = [{ + isIntersecting: true, + target: makeElement('true_id') + }, + { + isIntersecting: false, + target: makeElement('false_id') + }, + { + isIntersecting: false, + target: makeElement('false_id') + }]; + mockEntries[2].target.view_tracker = 8; + + const func = bidViewabilityIO.viewCallbackFactory(banner_bid); + func(mockEntries, mockObserver); + expect(mockEntries[0].target.view_tracker).to.be.a('number'); + expect(mockEntries[1].target.view_tracker).to.be.undefined; + expect(logMessageSpy.lastCall.lastArg).to.eq('bidViewabilityIO: viewable timer stopped for id: false_id code: banner_id'); + }); + }) +}); diff --git a/test/spec/modules/bidViewability_spec.js b/test/spec/modules/bidViewability_spec.js index 211dec090a5..a822d86f852 100644 --- a/test/spec/modules/bidViewability_spec.js +++ b/test/spec/modules/bidViewability_spec.js @@ -5,7 +5,7 @@ import * as utils from 'src/utils.js'; import * as sinon from 'sinon'; import {expect, spy} from 'chai'; import * as prebidGlobal from 'src/prebidGlobal.js'; -import { EVENTS } from 'src/constants.json'; +import CONSTANTS from 'src/constants.json'; import adapterManager, { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js'; import parse from 'url-parse'; @@ -279,9 +279,9 @@ describe('#bidViewability', function() { let call = callBidViewableBidderSpy.getCall(0); expect(call.args[0]).to.equal(PBJS_WINNING_BID.bidder); expect(call.args[1]).to.deep.equal(PBJS_WINNING_BID); - // EVENTS.BID_VIEWABLE is triggered + // CONSTANTS.EVENTS.BID_VIEWABLE is triggered call = eventsEmitSpy.getCall(0); - expect(call.args[0]).to.equal(EVENTS.BID_VIEWABLE); + expect(call.args[0]).to.equal(CONSTANTS.EVENTS.BID_VIEWABLE); expect(call.args[1]).to.deep.equal(PBJS_WINNING_BID); }); @@ -290,7 +290,7 @@ describe('#bidViewability', function() { expect(triggerPixelSpy.callCount).to.equal(0); // adapterManager.callBidViewableBidder is NOT called expect(callBidViewableBidderSpy.callCount).to.equal(0); - // EVENTS.BID_VIEWABLE is NOT triggered + // CONSTANTS.EVENTS.BID_VIEWABLE is NOT triggered expect(eventsEmitSpy.callCount).to.equal(0); }); }); diff --git a/test/spec/modules/bidfluenceBidAdapter_spec.js b/test/spec/modules/bidfluenceBidAdapter_spec.js deleted file mode 100644 index 6b3a0c2b044..00000000000 --- a/test/spec/modules/bidfluenceBidAdapter_spec.js +++ /dev/null @@ -1,114 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/bidfluenceBidAdapter.js'; - -const BIDDER_CODE = 'bidfluence'; -const PLACEMENT_ID = '1000'; -const PUB_ID = '1000'; -const CONSENT_STRING = 'DUXDSDFSFWRRR8345F=='; - -const validBidRequests = [{ - 'bidder': BIDDER_CODE, - 'params': { - 'placementId': PLACEMENT_ID, - 'publisherId': PUB_ID, - 'reservePrice': 0 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250]], - 'bidId': '2b1f23307fb8ef', - 'bidderRequestId': '10edf38ec1a719', - 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304' -}]; - -const bidderRequest = { - 'bidderCode': 'bidfluence', - 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304', - 'bidderRequestId': '10edf38ec1a719', - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'test', - 'stack': ['test'] - }, - 'timeout': 1000, - 'gdprConsent': { - 'gdprApplies': true, - 'consentString': CONSENT_STRING, - 'vendorData': '' - } -}; - -bidderRequest.bids = validBidRequests; - -describe('Bidfluence Adapter test', () => { - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(validBidRequests[0])).to.equal(true); - }); - it('should return the right bidder code', function () { - expect(spec.code).to.eql(BIDDER_CODE); - }); - }); - - describe('buildRequests', function () { - it('sends bid request to our endpoint via POST', function () { - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.method).to.equal('POST'); - const payload = JSON.parse(request.data); - - expect(payload.bids[0].bid).to.equal(validBidRequests[0].bidId); - expect(payload.azr).to.equal(true); - expect(payload.ck).to.not.be.undefined; - expect(payload.bids[0].tid).to.equal(PLACEMENT_ID); - expect(payload.bids[0].pid).to.equal(PUB_ID); - expect(payload.bids[0].rp).to.be.a('number'); - expect(payload.re).to.not.be.undefined; - expect(payload.st).to.not.be.undefined; - expect(payload.tz).to.not.be.undefined; - expect(payload.sr).to.not.be.undefined; - expect(payload.vp).to.not.be.undefined; - expect(payload.sdt).to.not.be.undefined; - expect(payload.bids[0].w).to.equal('300'); - expect(payload.bids[0].h).to.equal('250'); - }); - - it('sends gdpr info if exists', function () { - const request = spec.buildRequests(validBidRequests, bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.gdpr).to.equal(true); - expect(payload.gdprc).to.equal(CONSENT_STRING); - }); - }); - - describe('interpretResponse', function () { - const response = { - body: { - Bids: - [{ - 'CreativeId': '1000', - 'Cpm': 0.50, - 'Ad': '
', - 'Height': 250, - 'Width': 300 - }] - } - }; - - it('should get correct bid response', function () { - const expectedResponse = [{ - requestId: response.body.Bids[0].BidId, - cpm: response.body.Bids[0].Cpm, - width: response.body.Bids[0].Width, - height: response.body.Bids[0].Height, - creativeId: response.body.Bids[0].CreativeId, - ad: response.body.Bids[0].Ad, - currency: 'USD', - netRevenue: true, - ttl: 360 - }]; - - let result = spec.interpretResponse(response, { 'bidderRequest': validBidRequests[0] }); - expect(result).to.deep.equal(expectedResponse); - }); - }); -}); diff --git a/test/spec/modules/bidglassAdapter_spec.js b/test/spec/modules/bidglassAdapter_spec.js index d153430103d..7b007f7cc1f 100644 --- a/test/spec/modules/bidglassAdapter_spec.js +++ b/test/spec/modules/bidglassAdapter_spec.js @@ -65,7 +65,10 @@ describe('Bid Glass Adapter', function () { 'creativeId': '-1', 'width': '300', 'height': '250', - 'requestId': '30b31c1838de1e' + 'requestId': '30b31c1838de1e', + 'meta': { + 'advertiserDomains': ['https://example.com'] + } }] } }; @@ -83,7 +86,10 @@ describe('Bid Glass Adapter', function () { 'mediaType': 'banner', 'netRevenue': true, 'ttl': 10, - 'ad': '' + 'ad': '', + 'meta': { + 'advertiserDomains': ['https://example.com'] + } }]; let result = spec.interpretResponse(response); diff --git a/test/spec/modules/bidlabBidAdapter_spec.js b/test/spec/modules/bidlabBidAdapter_spec.js deleted file mode 100644 index cffd43ae6ca..00000000000 --- a/test/spec/modules/bidlabBidAdapter_spec.js +++ /dev/null @@ -1,235 +0,0 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/bidlabBidAdapter.js'; - -describe('BidlabBidAdapter', function () { - let bid = { - bidId: '23fhj33i987f', - bidder: 'bidlab', - params: { - placementId: 0, - traffic: 'banner' - } - }; - - describe('isBidRequestValid', function () { - it('Should return true if there are bidId, params and placementId parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; - }); - it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; - }); - }); - - describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid]); - it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequest).to.exist; - expect(serverRequest.method).to.exist; - expect(serverRequest.url).to.exist; - expect(serverRequest.data).to.exist; - }); - it('Returns POST method', function () { - expect(serverRequest.method).to.equal('POST'); - }); - it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://service.bidlab.ai/?c=o&m=multi'); - }); - it('Returns valid data if array of bids is valid', function () { - let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.language).to.be.a('string'); - expect(data.secure).to.be.within(0, 1); - expect(data.host).to.be.a('string'); - expect(data.page).to.be.a('string'); - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('banner'); - }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); - }); - describe('interpretResponse', function () { - it('Should interpret banner response', function () { - const banner = { - body: [{ - mediaType: 'banner', - width: 300, - height: 250, - cpm: 0.4, - ad: 'Test', - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] - }; - let bannerResponses = spec.interpretResponse(banner); - expect(bannerResponses).to.be.an('array').that.is.not.empty; - let dataItem = bannerResponses[0]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); - }); - it('Should interpret video response', function () { - const video = { - body: [{ - vastUrl: 'test.com', - mediaType: 'video', - cpm: 0.5, - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] - }; - let videoResponses = spec.interpretResponse(video); - expect(videoResponses).to.be.an('array').that.is.not.empty; - - let dataItem = videoResponses[0]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.5); - expect(dataItem.vastUrl).to.equal('test.com'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); - }); - it('Should interpret native response', function () { - const native = { - body: [{ - mediaType: 'native', - native: { - clickUrl: 'test.com', - title: 'Test', - image: 'test.com', - impressionTrackers: ['test.com'], - }, - ttl: 120, - cpm: 0.4, - requestId: '23fhj33i987f', - creativeId: '2', - netRevenue: true, - currency: 'USD', - }] - }; - let nativeResponses = spec.interpretResponse(native); - expect(nativeResponses).to.be.an('array').that.is.not.empty; - - let dataItem = nativeResponses[0]; - expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); - expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.native.clickUrl).to.equal('test.com'); - expect(dataItem.native.title).to.equal('Test'); - expect(dataItem.native.image).to.equal('test.com'); - expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; - expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); - }); - it('Should return an empty array if invalid banner response is passed', function () { - const invBanner = { - body: [{ - width: 300, - cpm: 0.4, - ad: 'Test', - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] - }; - - let serverResponses = spec.interpretResponse(invBanner); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - it('Should return an empty array if invalid video response is passed', function () { - const invVideo = { - body: [{ - mediaType: 'video', - cpm: 0.5, - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] - }; - let serverResponses = spec.interpretResponse(invVideo); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - it('Should return an empty array if invalid native response is passed', function () { - const invNative = { - body: [{ - mediaType: 'native', - clickUrl: 'test.com', - title: 'Test', - impressionTrackers: ['test.com'], - ttl: 120, - requestId: '23fhj33i987f', - creativeId: '2', - netRevenue: true, - currency: 'USD', - }] - }; - let serverResponses = spec.interpretResponse(invNative); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - it('Should return an empty array if invalid response is passed', function () { - const invalid = { - body: [{ - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] - }; - let serverResponses = spec.interpretResponse(invalid); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - }); - describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs(); - it('Returns valid URL and type', function () { - if (spec.noSync) { - expect(userSync).to.be.equal(false); - } else { - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.exist; - expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://service.bidlab.ai/?c=o&m=sync'); - } - }); - }); -}); diff --git a/test/spec/modules/bidphysicsBidAdapter_spec.js b/test/spec/modules/bidphysicsBidAdapter_spec.js deleted file mode 100644 index fc15c39cf81..00000000000 --- a/test/spec/modules/bidphysicsBidAdapter_spec.js +++ /dev/null @@ -1,261 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/bidphysicsBidAdapter.js'; - -const REQUEST = { - 'bidderCode': 'bidphysics', - 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708', - 'bidderRequestId': 'requestId', - 'bidRequest': [{ - 'bidder': 'bidphysics', - 'params': { - 'unitId': 123456, - }, - 'placementCode': 'div-gpt-dummy-placement-code', - 'sizes': [ - [300, 250] - ], - 'bidId': 'bidId1', - 'bidderRequestId': 'bidderRequestId', - 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' - }, - { - 'bidder': 'bidphysics', - 'params': { - 'unitId': 123456, - }, - 'placementCode': 'div-gpt-dummy-placement-code', - 'sizes': [ - [300, 250] - ], - 'bidId': 'bidId2', - 'bidderRequestId': 'bidderRequestId', - 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' - }], - 'start': 1487883186070, - 'auctionStart': 1487883186069, - 'timeout': 3000 -}; - -const RESPONSE = { - 'headers': null, - 'body': { - 'id': 'responseId', - 'seatbid': [ - { - 'bid': [ - { - 'id': 'bidId1', - 'impid': 'bidId1', - 'price': 0.18, - 'adm': '', - 'adid': '144762342', - 'adomain': [ - 'https://dummydomain.com' - ], - 'iurl': 'iurl', - 'cid': '109', - 'crid': 'creativeId', - 'cat': [], - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 334553, - 'auction_id': 514667951122925701, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - } - }, - { - 'id': 'bidId2', - 'impid': 'bidId2', - 'price': 0.1, - 'adm': '', - 'adid': '144762342', - 'adomain': [ - 'https://dummydomain.com' - ], - 'iurl': 'iurl', - 'cid': '109', - 'crid': 'creativeId', - 'cat': [], - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 386046, - 'auction_id': 517067951122925501, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - } - } - ], - 'seat': 'bidphysics' - } - ], - 'ext': { - 'usersync': { - 'sovrn': { - 'status': 'none', - 'syncs': [ - { - 'url': 'urlsovrn', - 'type': 'iframe' - } - ] - }, - 'appnexus': { - 'status': 'none', - 'syncs': [ - { - 'url': 'urlappnexus', - 'type': 'pixel' - } - ] - } - }, - 'responsetimemillis': { - 'appnexus': 127 - } - } - } -}; - -describe('BidPhysics bid adapter', function () { - describe('isBidRequestValid', function () { - it('should accept request if only unitId is passed', function () { - let bid = { - bidder: 'bidphysics', - params: { - unitId: 'unitId', - } - }; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - it('should accept request if only networkId is passed', function () { - let bid = { - bidder: 'bidphysics', - params: { - networkId: 'networkId', - } - }; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - it('should accept request if only publisherId is passed', function () { - let bid = { - bidder: 'bidphysics', - params: { - publisherId: 'publisherId', - } - }; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('reject requests without params', function () { - let bid = { - bidder: 'bidphysics', - params: {} - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - it('creates request data', function () { - let request = spec.buildRequests(REQUEST.bidRequest, REQUEST); - - expect(request).to.exist.and.to.be.a('object'); - const payload = JSON.parse(request.data); - expect(payload.imp[0]).to.have.property('id', REQUEST.bidRequest[0].bidId); - expect(payload.imp[1]).to.have.property('id', REQUEST.bidRequest[1].bidId); - }); - - it('has gdpr data if applicable', function () { - const req = Object.assign({}, REQUEST, { - gdprConsent: { - consentString: 'consentString', - gdprApplies: true, - } - }); - let request = spec.buildRequests(REQUEST.bidRequest, req); - - const payload = JSON.parse(request.data); - expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); - expect(payload.regs.ext).to.have.property('gdpr', 1); - }); - }); - - describe('interpretResponse', function () { - it('have bids', function () { - let bids = spec.interpretResponse(RESPONSE, REQUEST); - expect(bids).to.be.an('array').that.is.not.empty; - validateBidOnIndex(0); - validateBidOnIndex(1); - - function validateBidOnIndex(index) { - expect(bids[index]).to.have.property('currency', 'USD'); - expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].impid); - expect(bids[index]).to.have.property('cpm', RESPONSE.body.seatbid[0].bid[index].price); - expect(bids[index]).to.have.property('width', RESPONSE.body.seatbid[0].bid[index].w); - expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); - expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); - expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); - expect(bids[index]).to.have.property('ttl', 30); - expect(bids[index]).to.have.property('netRevenue', true); - } - }); - - it('handles empty response', function () { - const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); - const bids = spec.interpretResponse(EMPTY_RESP, REQUEST); - - expect(bids).to.be.empty; - }); - }); - - describe('getUserSyncs', function () { - it('handles no parameters', function () { - let opts = spec.getUserSyncs({}); - expect(opts).to.be.an('array').that.is.empty; - }); - it('returns non if sync is not allowed', function () { - let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); - - expect(opts).to.be.an('array').that.is.empty; - }); - - it('iframe sync enabled should return results', function () { - let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [RESPONSE]); - - expect(opts.length).to.equal(1); - expect(opts[0].type).to.equal('iframe'); - expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['sovrn'].syncs[0].url); - }); - - it('pixel sync enabled should return results', function () { - let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [RESPONSE]); - - expect(opts.length).to.equal(1); - expect(opts[0].type).to.equal('image'); - expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['appnexus'].syncs[0].url); - }); - - it('all sync enabled should return all results', function () { - let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [RESPONSE]); - - expect(opts.length).to.equal(2); - }); - }); -}); diff --git a/test/spec/modules/decenteradsBidAdapter_spec.js b/test/spec/modules/bidscubeBidAdapter_spec.js similarity index 78% rename from test/spec/modules/decenteradsBidAdapter_spec.js rename to test/spec/modules/bidscubeBidAdapter_spec.js index 257094cae3a..26cf19fc310 100644 --- a/test/spec/modules/decenteradsBidAdapter_spec.js +++ b/test/spec/modules/bidscubeBidAdapter_spec.js @@ -1,14 +1,15 @@ import { expect } from 'chai' -import { spec } from '../../../modules/decenteradsBidAdapter.js' +import { spec } from '../../../modules/bidscubeBidAdapter.js' import { deepStrictEqual, notEqual, ok, strictEqual } from 'assert' -describe('DecenteradsAdapter', () => { +describe('BidsCubeAdapter', () => { const bid = { bidId: '9ec5b177515ee2e5', - bidder: 'decenterads', + bidder: 'bidscube', params: { placementId: 0, - traffic: 'banner' + traffic: 'banner', + allParams: '{}' } } @@ -39,7 +40,7 @@ describe('DecenteradsAdapter', () => { }) it('Returns valid URL', () => { - strictEqual('https://supply.decenterads.com/?c=o&m=multi', serverRequest.url) + strictEqual('https://supply.bidscube.com/?c=o&m=multi', serverRequest.url) }) it('Returns valid data if array of bids is valid', () => { @@ -54,10 +55,11 @@ describe('DecenteradsAdapter', () => { notEqual(-1, [0, 1].indexOf(data.secure)) const placement = data.placements[0] - deepStrictEqual(['placementId', 'bidId', 'traffic'], Object.keys(placement)) + deepStrictEqual(['placementId', 'bidId', 'traffic', 'allParams'], Object.keys(placement)) strictEqual(0, placement.placementId) strictEqual('9ec5b177515ee2e5', placement.bidId) strictEqual('banner', placement.traffic) + strictEqual('{"bidId":"9ec5b177515ee2e5","bidder":"bidscube","params":{"placementId":0,"traffic":"banner","allParams":"{}"}}', placement.allParams) }) it('Returns empty data if no valid requests are passed', () => { @@ -82,12 +84,15 @@ describe('DecenteradsAdapter', () => { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['test.com'] + } }] }, { body: [{ - vastUrl: 'decenterads.com', + vastUrl: 'bidscube.com', mediaType: 'video', cpm: 0.5, requestId: '9ec5b177515ee2e5', @@ -95,22 +100,28 @@ describe('DecenteradsAdapter', () => { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['test.com'] + } }] }, { body: [{ mediaType: 'native', - clickUrl: 'decenterads.com', + clickUrl: 'bidscube.com', title: 'Test', - image: 'decenterads.com', + image: 'bidscube.com', creativeId: '2', - impressionTrackers: ['decenterads.com'], + impressionTrackers: ['bidscube.com'], ttl: 120, cpm: 0.4, requestId: '9ec5b177515ee2e5', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['test.com'] + } }] } ] @@ -125,11 +136,19 @@ describe('DecenteradsAdapter', () => { strictEqual(1, response.length) const copy = { ...obj.body[0] } - delete copy.mediaType deepStrictEqual(copy, response[0]) }) } + for (const obj of validData) { + it(`Should interpret response has meta.advertiserDomains`, () => { + const response = spec.interpretResponse(obj) + + expect(response[0]['meta']['advertiserDomains']).to.be.an('array') + expect(response[0]['meta']['advertiserDomains'][0]).to.be.an('string') + }) + } + const invalidData = [ { body: [{ @@ -159,9 +178,9 @@ describe('DecenteradsAdapter', () => { { body: [{ mediaType: 'native', - clickUrl: 'decenterads.com', + clickUrl: 'bidscube.com', title: 'Test', - impressionTrackers: ['decenterads.com'], + impressionTrackers: ['bidscube.com'], ttl: 120, requestId: '9ec5b177515ee2e5', creativeId: '2', @@ -200,7 +219,7 @@ describe('DecenteradsAdapter', () => { describe('getUserSyncs', () => { it('Returns valid URL and type', () => { - const expectedResult = [{ type: 'image', url: 'https://supply.decenterads.com/?c=o&m=cookie' }] + const expectedResult = [{ type: 'image', url: 'https://supply.bidscube.com/?c=o&m=cookie' }] deepStrictEqual(expectedResult, spec.getUserSyncs()) }) }) diff --git a/test/spec/modules/big-richmediaBidAdapter_spec.js b/test/spec/modules/big-richmediaBidAdapter_spec.js new file mode 100644 index 00000000000..1e97e1ac1d7 --- /dev/null +++ b/test/spec/modules/big-richmediaBidAdapter_spec.js @@ -0,0 +1,310 @@ +import { expect } from 'chai'; +import { spec } from 'modules/big-richmediaBidAdapter.js'; +import { auctionManager } from 'src/auctionManager.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { deepClone } from 'src/utils.js'; + +describe('bigRichMediaAdapterTests', function () { + before(function () { + config.setConfig({ + bigRichmedia: { + publisherId: '123ABC' + } + }); + }); + + after(function () { + config.resetConfig(); + }); + + describe('bidRequestValidity', function () { + const bid = { + 'bidder': 'bigRichmedia', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('bidRequest with zoneId and deliveryUrl params', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('bidRequest with no params is not valid', function () { + const localBid = Object.assign({}, bid); + localBid.params = {}; + expect(spec.isBidRequestValid(localBid)).to.equal(false); + }); + }); + + describe('bidRequest', function () { + let getAdUnitsStub; + const bidRequests = [ + { + 'bidder': 'bigRichmedia', + 'params': { + 'placementId': '10433394' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600], [1800, 1000]] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600], [1800, 1000]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + + beforeEach(function() { + getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { + return []; + }); + }); + + afterEach(function() { + getAdUnitsStub.restore(); + }); + + it('should have skin size', function () { + const bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + format: 'skin' + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].sizes).to.exist; + expect(payload.tags[0].sizes).to.have.lengthOf(3); + }); + + it('should build video bid request', function() { + const bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream', + format: 'sticky-top' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + }); + + describe('interpretResponse', function () { + let bfStub; + + before(function() { + bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); + }); + + after(function() { + bfStub.restore(); + }); + + const response = { + 'version': '3.0.0', + 'tags': [ + { + 'uuid': '3db3773286ee59', + 'tag_id': 10433394, + 'auction_id': '4534722592064951574', + 'nobid': false, + 'no_ad_url': 'https://lax1-ib.adnxs.com/no-ad', + 'timeout_ms': 10000, + 'ad_profile_id': 27079, + 'ads': [ + { + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 958, + 'creative_id': 29681110, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 0.5, + 'cpm_publisher_currency': 0.5, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'viewability': { + 'config': '' + }, + 'rtb': { + 'banner': { + 'content': '', + 'width': 300, + 'height': 250 + }, + 'trackers': [ + { + 'impression_urls': [ + 'https://lax1-ib.adnxs.com/impression', + 'https://www.test.com/tracker' + ], + 'video_events': {} + } + ] + } + } + ] + } + ] + }; + + it('should get correct bid response', function () { + const expectedResponse = [ + { + 'requestId': '3db3773286ee59', + 'cpm': 0.5, + 'creativeId': 29681110, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'netRevenue': true, + 'adUnitCode': 'code', + 'appnexus': { + 'buyerMemberId': 958 + }, + 'meta': { + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [{ + 'bsid': '958' + }] + } + } + } + ]; + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + }; + const result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles outstream video responses', function () { + const response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + const bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + } + + const result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).not.to.have.property('vastXml'); + expect(result[0]).not.to.have.property('vastUrl'); + expect(result[0]).to.have.property('width', 1); + expect(result[0]).to.have.property('height', 1); + expect(result[0]).to.have.property('mediaType', 'banner'); + }); + }); + + describe('getUserSyncs', function() { + const syncOptions = { + syncEnabled: false + }; + + it('should not return sync', function() { + const serverResponse = [{ body: '' }]; + const result = spec.getUserSyncs(syncOptions, serverResponse); + expect(result).to.be.undefined; + }); + }); + + describe('transformBidParams', function() { + it('cast placementId to number', function() { + const adUnit = { + code: 'adunit-code', + params: { + placementId: '456' + } + }; + const bid = { + params: { + placementId: '456' + }, + sizes: [[300, 250]], + mediaTypes: { + banner: { sizes: [[300, 250]] } + } + }; + + const params = spec.transformBidParams({ placementId: '456' }, true, adUnit, [{ bidderCode: 'bigRichmedia', auctionId: bid.auctionId, bids: [bid] }]); + + expect(params.placement_id).to.exist; + expect(params.placement_id).to.be.a('number'); + }); + }); + + describe('onBidWon', function() { + it('Should not have any error', function() { + const result = spec.onBidWon({}); + expect(true).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/bizzclickBidAdapter_spec.js index e0698c9eda8..f80051b0a50 100644 --- a/test/spec/modules/bizzclickBidAdapter_spec.js +++ b/test/spec/modules/bizzclickBidAdapter_spec.js @@ -48,6 +48,17 @@ const BANNER_BID_REQUEST = { sizes: [[300, 250], [300, 600]] } }, + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '164', + hp: 1 + } + ] + }, bidder: 'bizzclick', params: { placementId: 'hash', @@ -252,6 +263,11 @@ describe('BizzclickAdapter', function() { expect(request.data[0].regs.ext.us_privacy).to.equal(BANNER_BID_REQUEST.uspConsent); }) + it('check schain is set properly', function() { + expect(request.data[0].source.ext.schain.complete).to.equal(1); + expect(request.data[0].source.ext.schain.ver).to.equal('1.0'); + }) + it('Returns valid URL', function () { expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); }); @@ -299,6 +315,8 @@ describe('BizzclickAdapter', function() { netRevenue: true, creativeId: BANNER_BID_RESPONSE.seatbid[0].bid[0].crid, dealId: BANNER_BID_RESPONSE.seatbid[0].bid[0].dealid, + + meta: {advertiserDomains: BANNER_BID_RESPONSE.seatbid[0].bid[0].adomain}, mediaType: 'banner', ad: BANNER_BID_RESPONSE.seatbid[0].bid[0].adm } @@ -308,11 +326,12 @@ describe('BizzclickAdapter', function() { expect(bannerResponses).to.be.an('array').that.is.not.empty; let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'meta', 'mediaType'); expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); expect(dataItem.ad).to.equal(expectedBidResponse.ad); expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.meta.advertiserDomains).to.equal(expectedBidResponse.meta.advertiserDomains); expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal(expectedBidResponse.currency); @@ -337,6 +356,7 @@ describe('BizzclickAdapter', function() { dealId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].dealid, mediaType: 'video', vastXml: VIDEO_BID_RESPONSE.seatbid[0].bid[0].adm, + meta: {advertiserDomains: VIDEO_BID_RESPONSE.seatbid[0].bid[0].adomain}, vastUrl: VIDEO_BID_RESPONSE.seatbid[0].bid[0].ext.vastUrl } @@ -345,12 +365,13 @@ describe('BizzclickAdapter', function() { expect(videoResponses).to.be.an('array').that.is.not.empty; let dataItem = videoResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'meta', 'mediaType'); expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); expect(dataItem.vastXml).to.equal(expectedBidResponse.vastXml) expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.meta.advertiserDomains).to.equal(expectedBidResponse.meta.advertiserDomains); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal(expectedBidResponse.currency); expect(dataItem.width).to.equal(expectedBidResponse.width); @@ -373,6 +394,7 @@ describe('BizzclickAdapter', function() { creativeId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].crid, dealId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].dealid, mediaType: 'native', + meta: {advertiserDomains: NATIVE_BID_RESPONSE.seatbid[0].bid[0].adomain}, native: {clickUrl: NATIVE_BID_RESPONSE.seatbid[0].bid[0].adm.native.link.url} } @@ -381,9 +403,10 @@ describe('BizzclickAdapter', function() { expect(nativeResponses).to.be.an('array').that.is.not.empty; let dataItem = nativeResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'native', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.meta.advertiserDomains).to.equal(expectedBidResponse.meta.advertiserDomains); expect(dataItem.native.clickUrl).to.equal(expectedBidResponse.native.clickUrl) expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js new file mode 100644 index 00000000000..729605f7db8 --- /dev/null +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -0,0 +1,612 @@ +import { expect } from 'chai' +import { spec, buildBid, BLIINK_ENDPOINT_ENGINE, parseXML, getMetaList } from 'modules/bliinkBidAdapter.js' + +/** + * @description Mockup bidRequest + * @return {{ + * bidderWinsCount: number, + * adUnitCode: string, + * bidder: string, + * src: string, + * bidRequestsCount: number, + * params: {tagId: string, placement: string}, + * bidId: string, + * transactionId: string, + * auctionId: string, + * bidderRequestId: string, + * bidderRequestsCount: number, + * mediaTypes: {banner: {sizes: number[][]}}, + * sizes: number[][], + * crumbs: {pubcid: string}, + * ortb2Imp: {ext: {data: {pbadslot: string}}}}} + */ +const getConfigBid = (placement) => { + return { + adUnitCode: '/19968336/test', + auctionId: '6752b51c-dcd4-4001-85dc-885ab5c504cf', + bidId: '2def0c5b2a7f6e', + bidRequestsCount: 1, + bidder: 'bliink', + bidderRequestId: '1592eb20088b18', + bidderRequestsCount: 1, + bidderWinsCount: 0, + crumbs: { + pubcid: '55ffadc5-051f-428d-8ecc-dc585e0bde0d' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + ortb2Imp: { + ext: { + data: { + pbadslot: '/19968336/test' + } + } + }, + params: { + placement: placement, + tagId: '14f30eca-85d2-11e8-9eed-0242ac120007' + }, + sizes: [ + [300, 250] + ], + src: 'client', + transactionId: 'cc6678c4-9746-4082-b9e2-d8065d078ebf' + } +} + +/** + * @description Mockup response from engine.bliink.io/xxxx + * @return { + * { + * viewability_percent_in_view: number, + * viewability_duration: number, + * ad_id: number, + * id: number, + * category: number, + * type: number, + * content: { + * creative: { + * adm: string + * } + * } + * } + * } +* } + */ +const getConfigCreative = () => { + return { + ad_id: 5648, + price: 1, + currency: 'EUR', + media_type: 'banner', + category: 1, + id: 2825, + creativeId: 2825, + type: 1, + viewability_duration: 1, + viewability_percent_in_view: 30, + content: { + creative: { + adm: '', + } + } + } +} + +const getConfigCreativeVideo = () => { + return { + ad_id: 5648, + price: 1, + currency: 'EUR', + media_type: 'video', + category: 1, + creativeId: 2825, + content: '' + } +} + +/** + * @description Mockup BuildRequest function + * @return {{bidderRequestId: string, bidderCode: string, bids: {bidderWinsCount: number, adUnitCode: string, bidder: string, src: string, bidRequestsCount: number, params: {tagId: string, placement: string}, bidId: string, transactionId: string, auctionId: string, bidderRequestId: string, bidderRequestsCount: number, mediaTypes: {banner: {sizes: number[][]}}, sizes: number[][], crumbs: {pubcid: string}, ortb2Imp: {ext: {data: {pbadslot: string}}}}[], refererInfo: {referer: string, canonicalUrl: null, isAmp: boolean, reachedTop: boolean, numIframes: number}}} + */ +const getConfigBuildRequest = (placement) => { + let buildRequest = { + bidderRequestId: '164ddfd207e94d', + bidderCode: 'bliink', + bids: [getConfigBid(placement)], + refererInfo: { + canonicalUrl: null, + isAmp: false, + numIframes: 0, + reachedTop: true, + referer: 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + }, + } + + if (!placement) { + return buildRequest + } + + return Object.assign(buildRequest, { + params: { + bids: [getConfigBid(placement)], + placement: placement + }, + }) +} + +/** + * @description Mockup response from API + * @param noAd + * @return {{mode: string, message: string}|{headers: {}, body: {mode: string, creative: {viewability_percent_in_view: number, viewability_duration: number, ad_id: number, adm: string, id: number, category: number, type: number}, token: string}}} + */ +const getConfigInterpretResponse = (noAd = false) => { + if (noAd) { + return { + message: 'invalid tag', + mode: 'no-ad' + } + } + + return { + body: { + creative: getConfigCreative(), + mode: 'ad', + token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjgxNzA4MzEsImlhdCI6MTYyNzU2NjAzMSwiaXNzIjoiYmxpaW5rIiwiZGF0YSI6eyJ0eXBlIjoiYWQtc2VydmVyIiwidHJhbnNhY3Rpb25JZCI6IjM1YmU1NDNjLTNkZTQtNGQ1Yy04N2NjLWIzYzEyOGZiYzU0MCIsIm5ldHdvcmtJZCI6MjEsInNpdGVJZCI6NTksInRhZ0lkIjo1OSwiY29va2llSWQiOiJjNGU4MWVhOS1jMjhmLTQwZDItODY1ZC1hNjQzZjE1OTcyZjUiLCJldmVudElkIjozLCJ0YXJnZXRpbmciOnsicGxhdGZvcm0iOiJXZWJzaXRlIiwiaXAiOiI3OC4xMjIuNzUuNzIiLCJ0aW1lIjoxNjI3NTY2MDMxLCJsb2NhdGlvbiI6eyJsYXRpdHVkZSI6NDguOTczOSwibG9uZ2l0dWRlIjozLjMxMTMsInJlZ2lvbiI6IkhERiIsImNvdW50cnkiOiJGUiIsImNpdHkiOiJTYXVsY2hlcnkiLCJ6aXBDb2RlIjoiMDIzMTAiLCJkZXBhcnRtZW50IjoiMDIifSwiY2l0eSI6IlNhdWxjaGVyeSIsImNvdW50cnkiOiJGUiIsImRldmljZU9zIjoibWFjT1MiLCJkZXZpY2VQbGF0Zm9ybSI6IldlYnNpdGUiLCJyYXdVc2VyQWdlbnQiOiJNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvOTEuMC40NDcyLjEyNCBTYWZhcmkvNTM3LjM2In0sImdkcHIiOnsiaGFzQ29uc2VudCI6dHJ1ZX0sIndpbiI6ZmFsc2UsImFkSWQiOjU2NDgsImFkdmVydGlzZXJJZCI6MSwiY2FtcGFpZ25JZCI6MSwiY3JlYXRpdmVJZCI6MjgyNSwiZXJyb3IiOmZhbHNlfX0.-UefQH4G0k-RJGemBYffs-KL7EEwma2Wuwgk2xnpij8' + }, + headers: {}, + } +} + +/** + * @description Mockup response from API for RTB creative + * @param noAd + * @return {{body: string} | {mode: string, message: string}} + */ +const getConfigInterpretResponseRTB = (noAd = false) => { + if (noAd) { + return { + message: 'invalid tag', + mode: 'no-ad' + } + } + + return { + body: ` + + + + BLIINK + https://vast.bliink.io/p/508379d0-9f65-4198-8ba5-f61f2b51224f.xml + https://e.api.bliink.io/e?name=vast-error&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzQwMzA1MjcsImlhdCI6MTYzMzQyNTcyNywiaXNzIjoiYmxpaW5rIiwiZGF0YSI6eyJ0eXBlIjoiYWQtc2VydmVyIiwidHJhbnNhY3Rpb25JZCI6ImE2NjJjZGJmLTkzNDYtNDI0MS1iMTU0LTJhOTc2OTg0NjNmOSIsIm5ldHdvcmtJZCI6MjUsInNpdGVJZCI6MTQzLCJ0YWdJZCI6MTI3MSwiY29va2llSWQiOiIwNWFhN2UwMi05MzgzLTQ1NGYtOTJmZC1jOTE2YWNlMmUyZjYiLCJldmVudElkIjozLCJ0YXJnZXRpbmciOnsicGxhdGZvcm0iOiJXZWJzaXRlIiwicmVmZXJyZXIiOiJodHRwOi8vbG9jYWxob3N0OjgxODEvaW50ZWdyYXRpb25FeGFtcGxlcy9ncHQvYmxpaW5rLWluc3RyZWFtLmh0bWwiLCJwYWdlVXJsIjoiaHR0cDovL2xvY2FsaG9zdDo4MTgxL2ludGVncmF0aW9uRXhhbXBsZXMvZ3B0L2JsaWluay1pbnN0cmVhbS5odG1sIiwiaXAiOiIzMS4zOS4xNDEuMTQwIiwidGltZSI6MTYzMzQyNTcyNywibG9jYXRpb24iOnsibGF0aXR1ZGUiOjQ4Ljk0MjIsImxvbmdpdHVkZSI6Mi41MDM5LCJyZWdpb24iOiJJREYiLCJjb3VudHJ5IjoiRlIiLCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsInppcENvZGUiOiI5MzYwMCIsImRlcGFydG1lbnQiOiI5MyJ9LCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsImNvdW50cnkiOiJGUiIsImRldmljZU9zIjoibWFjT1MiLCJkZXZpY2VQbGF0Zm9ybSI6IldlYnNpdGUiLCJyYXdVc2VyQWdlbnQiOiJNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvOTMuMC40NTc3LjYzIFNhZmFyaS81MzcuMzYiLCJjb250ZW50Q2xhc3NpZmljYXRpb24iOnsiYnJhbmRzYWZlIjpmYWxzZX19LCJnZHByIjp7Imhhc0NvbnNlbnQiOnRydWV9LCJ3aW4iOmZhbHNlLCJhZElkIjo1NzkzLCJhZHZlcnRpc2VySWQiOjEsImNhbXBhaWduSWQiOjEsImNyZWF0aXZlSWQiOjExOTQsImVycm9yIjpmYWxzZX19.nJSJPKovg0_jSHtLdrMPDqesAIlFKCuXPXYxpsyWBDw + https://e.api.bliink.io/e?name=impression&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzQwMzA1MjcsImlhdCI6MTYzMzQyNTcyNywiaXNzIjoiYmxpaW5rIiwiZGF0YSI6eyJ0eXBlIjoiYWQtc2VydmVyIiwidHJhbnNhY3Rpb25JZCI6ImE2NjJjZGJmLTkzNDYtNDI0MS1iMTU0LTJhOTc2OTg0NjNmOSIsIm5ldHdvcmtJZCI6MjUsInNpdGVJZCI6MTQzLCJ0YWdJZCI6MTI3MSwiY29va2llSWQiOiIwNWFhN2UwMi05MzgzLTQ1NGYtOTJmZC1jOTE2YWNlMmUyZjYiLCJldmVudElkIjozLCJ0YXJnZXRpbmciOnsicGxhdGZvcm0iOiJXZWJzaXRlIiwicmVmZXJyZXIiOiJodHRwOi8vbG9jYWxob3N0OjgxODEvaW50ZWdyYXRpb25FeGFtcGxlcy9ncHQvYmxpaW5rLWluc3RyZWFtLmh0bWwiLCJwYWdlVXJsIjoiaHR0cDovL2xvY2FsaG9zdDo4MTgxL2ludGVncmF0aW9uRXhhbXBsZXMvZ3B0L2JsaWluay1pbnN0cmVhbS5odG1sIiwiaXAiOiIzMS4zOS4xNDEuMTQwIiwidGltZSI6MTYzMzQyNTcyNywibG9jYXRpb24iOnsibGF0aXR1ZGUiOjQ4Ljk0MjIsImxvbmdpdHVkZSI6Mi41MDM5LCJyZWdpb24iOiJJREYiLCJjb3VudHJ5IjoiRlIiLCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsInppcENvZGUiOiI5MzYwMCIsImRlcGFydG1lbnQiOiI5MyJ9LCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsImNvdW50cnkiOiJGUiIsImRldmljZU9zIjoibWFjT1MiLCJkZXZpY2VQbGF0Zm9ybSI6IldlYnNpdGUiLCJyYXdVc2VyQWdlbnQiOiJNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvOTMuMC40NTc3LjYzIFNhZmFyaS81MzcuMzYiLCJjb250ZW50Q2xhc3NpZmljYXRpb24iOnsiYnJhbmRzYWZlIjpmYWxzZX19LCJnZHByIjp7Imhhc0NvbnNlbnQiOnRydWV9LCJ3aW4iOmZhbHNlLCJhZElkIjo1NzkzLCJhZHZlcnRpc2VySWQiOjEsImNhbXBhaWduSWQiOjEsImNyZWF0aXZlSWQiOjExOTQsImVycm9yIjpmYWxzZX19.nJSJPKovg0_jSHtLdrMPDqesAIlFKCuXPXYxpsyWBDw + 1EUR + + + + ` + } +} + +/** + * + * + * + * @description Below start tests for utils fonctions + * + * + * + */ + +const testsGetMetaList = [ + { + title: 'Should return empty array if there are no parameters', + args: { + fn: getMetaList() + }, + want: [] + }, + { + title: 'Should return list of metas with name associated', + args: { + fn: getMetaList('test'), + }, + want: [ + { + key: 'name', + value: 'test', + }, + { + key: 'name*', + value: 'test', + }, + { + key: 'itemprop*', + value: 'test', + }, + { + key: 'property', + value: `'og:${'test'}'`, + }, + { + key: 'property', + value: `'twitter:${'test'}'`, + }, + { + key: 'property', + value: `'article:${'test'}'`, + }, + ] + } +] + +describe('BLIINK Adapter getMetaList', function() { + for (const test of testsGetMetaList) { + it(test.title, () => { + const res = test.args.fn + expect(res).to.eql(test.want) + }) + } +}) + +/** + * @description Array of tests used in describe function below + * @type {[{args: {fn: (string|Document)}, want: string, title: string}, {args: {fn: (string|Document)}, want: string, title: string}]} + */ +const testsParseXML = [ + { + title: 'Should return null, if content length equal to 0', + args: { + fn: parseXML('') + }, + want: null, + }, + { + title: 'Should return null, if content isnt string', + args: { + fn: parseXML({}) + }, + want: null, + }, +] + +describe('BLIINK Adapter parseXML', function() { + for (const test of testsParseXML) { + it(test.title, () => { + const res = test.args.fn + expect(res).to.eql(test.want) + }) + } +}) + +/** + * + * + * + * @description End tests for utils fonctions + * + * + * + */ + +/** + * @description Array of tests used in describe function below + * @type {[{args: {fn}, want: boolean, title: string}, {args: {fn}, want: boolean, title: string}, {args: {fn}, want: boolean, title: string}]} + */ +const testsIsBidRequestValid = [ + { + title: 'isBidRequestValid format not valid', + args: { + fn: spec.isBidRequestValid({}) + }, + want: false, + }, + { + title: 'isBidRequestValid does not receive any bid', + args: { + fn: spec.isBidRequestValid() + }, + want: false, + }, + { + title: 'isBidRequestValid Receive a valid bid', + args: { + fn: spec.isBidRequestValid(getConfigBid('banner')) + }, + want: true, + } +] + +describe('BLIINK Adapter isBidRequestValid', function() { + for (const test of testsIsBidRequestValid) { + it(test.title, () => { + const res = test.args.fn + expect(res).to.eql(test.want) + }) + } +}) + +const testsInterpretResponse = [ + { + title: 'Should construct bid for video instream', + args: { + fn: spec.interpretResponse(getConfigInterpretResponseRTB(false), getConfigBuildRequest('video')) + }, + want: { + cpm: 0, + currency: 'EUR', + height: 250, + width: 300, + creativeId: 0, + mediaType: 'video', + netRevenue: false, + requestId: '2def0c5b2a7f6e', + ttl: 3600, + vastXml: getConfigInterpretResponseRTB().body, + } + }, + { + title: 'ServerResponse with message: invalid tag, return empty array', + args: { + fn: spec.interpretResponse(getConfigInterpretResponse(true), getConfigBuildRequest('banner')) + }, + want: [] + }, +] + +describe('BLIINK Adapter interpretResponse', function() { + for (const test of testsInterpretResponse) { + it(test.title, () => { + const res = test.args.fn + + if (res) { + expect(res).to.eql(test.want) + } + }) + } +}) + +/** + * @description Array of tests used in describe function below + * @type {[ + * {args: + * {fn: { + * cpm: number, + * netRevenue: boolean, + * ad, requestId, + * meta: {mediaType}, + * width: number, + * currency: string, + * ttl: number, + * creativeId: number, + * height: number + * } + * }, want, title: string}]} + */ +const testsBuildBid = [ + { + title: 'Should return null if no bid passed in parameters', + args: { + fn: buildBid() + }, + want: null + }, + { + title: 'Input data must respect the output model', + args: { + fn: buildBid({ id: 1, test: '123' }, { id: 2, test: '345' }, false, false) + }, + want: null + }, + { + title: 'input data respect the output model for video', + args: { + fn: buildBid(getConfigBid('video'), getConfigCreativeVideo()) + }, + want: { + requestId: getConfigBid('video').bidId, + cpm: 1, + currency: 'EUR', + mediaType: 'video', + width: 300, + height: 250, + creativeId: getConfigCreativeVideo().creativeId, + netRevenue: false, + vastXml: getConfigCreativeVideo().content, + ttl: 3600, + } + }, + { + title: 'input data respect the output model for banner', + args: { + fn: buildBid(getConfigBid('banner'), getConfigCreative()) + }, + want: { + requestId: getConfigBid('banner').bidId, + cpm: 1, + currency: 'EUR', + mediaType: 'banner', + width: 300, + height: 250, + creativeId: getConfigCreative().id, + ad: getConfigCreative().content.creative.adm, + ttl: 3600, + netRevenue: false, + } + } +] + +describe('BLIINK Adapter buildBid', function() { + for (const test of testsBuildBid) { + it(test.title, () => { + const res = test.args.fn + expect(res).to.eql(test.want) + }) + } +}) + +/** + * @description Array of tests used in describe function below + * @type {[{args: {fn}, want, title: string}]} + */ +const testsBuildRequests = [ + { + title: 'Should not build request, no bidder request exist', + args: { + fn: spec.buildRequests() + }, + want: null + }, + { + title: 'Should build request if bidderRequest exist', + args: { + fn: spec.buildRequests([], getConfigBuildRequest('banner')) + }, + want: { + method: 'GET', + url: `${BLIINK_ENDPOINT_ENGINE}/${getConfigBuildRequest('banner').bids[0].params.tagId}`, + params: { + bidderRequestId: getConfigBuildRequest('banner').bidderRequestId, + bidderCode: getConfigBuildRequest('banner').bidderCode, + bids: getConfigBuildRequest('banner').bids, + refererInfo: getConfigBuildRequest('banner').refererInfo + }, + data: { + gdpr: false, + gdpr_consent: '', + height: 250, + width: 300, + keywords: '', + pageDescription: '', + pageTitle: '', + pageUrl: 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + } + } + }, + { + title: 'Should build request width GDPR configuration', + args: { + fn: spec.buildRequests([], Object.assign(getConfigBuildRequest('banner'), { + gdprConsent: { + gdprApplies: true, + consentString: 'XXXX' + }, + })) + }, + want: { + method: 'GET', + url: `${BLIINK_ENDPOINT_ENGINE}/${getConfigBuildRequest('banner').bids[0].params.tagId}`, + params: { + bidderRequestId: getConfigBuildRequest('banner').bidderRequestId, + bidderCode: getConfigBuildRequest('banner').bidderCode, + bids: getConfigBuildRequest('banner').bids, + refererInfo: getConfigBuildRequest('banner').refererInfo + }, + data: { + gdpr: true, + gdpr_consent: 'XXXX', + pageDescription: '', + pageTitle: '', + keywords: '', + pageUrl: 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + height: 250, + width: 300, + } + } + } +] + +describe('BLIINK Adapter buildRequests', function() { + for (const test of testsBuildRequests) { + it(test.title, () => { + const res = test.args.fn + expect(res).to.eql(test.want) + }) + } +}) + +const getSyncOptions = (pixelEnabled = true, iframeEnabled = 'false') => { + return { + pixelEnabled, + iframeEnabled + } +} + +const getServerResponses = () => { + return [ + { + body: '', + } + ] +} + +const getGdprConsent = () => { + return { + gdprApplies: 1, + consentString: 'XXX' + } +} + +const testsGetUserSyncs = [ + { + title: 'Should not have gdprConsent exist', + args: { + fn: spec.getUserSyncs(getSyncOptions(), getServerResponses(), getGdprConsent()) + }, + want: [ + { + type: 'script', + url: 'https://prg.smartadserver.com/ac?out=js&nwid=3392&siteid=305791&pgname=rg&fmtid=81127&tgt=[sas_target]&visit=m&tmstp=[timestamp]&clcturl=[countgo]' + }, + { + type: 'image', + url: 'https://sync.smartadserver.com/getuid?nwid=3392&consentString=XXX&url=https%3A%2F%2Fcookiesync.api.bliink.io%2Fcookiesync%3Fpartner%3Dsmart%26uid%3D%5Bsas_uid%5D' + }, + { + type: 'image', + url: 'https://ad.360yield.com/server_match?partner_id=1531&consentString=XXX&r=https%3A%2F%2Fcookiesync.api.bliink.io%2Fcookiesync%3Fpartner%3Dazerion%26uid%3D%7BPUB_USER_ID%7D', + }, + { + type: 'image', + url: 'https://ads.stickyadstv.com/auto-user-sync?consentString=XXX', + }, + { + type: 'image', + url: 'https://cookiesync.api.bliink.io/getuid?url=https%3A%2F%2Fvisitor.omnitagjs.com%2Fvisitor%2Fsync%3Fuid%3D1625272249969090bb9d544bd6d8d645%26name%3DBLIINK%26visitor%3D%24UID%26external%3Dtrue&consentString=XXX', + }, + { + type: 'image', + url: 'https://cookiesync.api.bliink.io/getuid?url=https://pixel.advertising.com/ups/58444/sync?&gdpr=1&gdpr_consent=XXX&redir=true&uid=$UID', + }, + { + type: 'image', + url: 'https://ups.analytics.yahoo.com/ups/58499/occ?gdpr=1&gdpr_consent=XXX', + }, + { + type: 'image', + url: 'https://secure.adnxs.com/getuid?https%3A%2F%2Fcookiesync.api.bliink.io%2Fcookiesync%3Fpartner%3Dazerion%26uid%3D%24UID', + }, + ] + }, + { + title: 'Should not have gdpr consent', + args: { + fn: spec.getUserSyncs(getSyncOptions(), getServerResponses()) + }, + want: [] + } +] + +describe('BLIINK Adapter getUserSyncs', function() { + for (const test of testsGetUserSyncs) { + it(test.title, () => { + const res = test.args.fn + expect(res).to.eql(test.want) + }) + } +}) diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js index 3aee6c69438..c831ddf6ddf 100644 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -760,6 +760,10 @@ describe('BlueBillywigAdapter', () => { expect(bid.currency).to.equal(serverResponse.body.cur); expect(bid.ttl).to.equal(BB_CONSTANTS.DEFAULT_TTL); + expect(bid).to.have.property('meta'); + expect(bid.meta).to.have.property('advertiserDomains'); + expect(bid.meta.advertiserDomains[0]).to.equal('bluebillywig.com'); + expect(bid.publicationName).to.equal(validBidderRequest.bids[0].params.publicationName); expect(bid.rendererCode).to.equal(validBidderRequest.bids[0].params.rendererCode); expect(bid.accountId).to.equal(validBidderRequest.bids[0].params.accountId); diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js index a353665ec33..afb5f935621 100644 --- a/test/spec/modules/boldwinBidAdapter_spec.js +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -6,9 +6,13 @@ describe('BoldwinBidAdapter', function () { const bid = { bidId: '23fhj33i987f', bidder: 'boldwin', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, params: { - placementId: 0, - traffic: BANNER + placementId: 'testBanner', } }; @@ -40,7 +44,7 @@ describe('BoldwinBidAdapter', function () { expect(serverRequest.method).to.equal('POST'); }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://ssp.videowalldirect.com/?c=o&m=multi'); + expect(serverRequest.url).to.equal('https://ssp.videowalldirect.com/pbjs'); }); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; @@ -55,17 +59,16 @@ describe('BoldwinBidAdapter', function () { expect(data.gdpr).to.not.exist; expect(data.ccpa).to.not.exist; let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'hPlayer', 'wPlayer', 'schain'); - expect(placement.placementId).to.equal(0); + expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'bidFloor'); + expect(placement.placementId).to.equal('testBanner'); expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal(BANNER); + expect(placement.adFormat).to.equal(BANNER); expect(placement.schain).to.be.an('object'); }); it('Returns valid data for mediatype video', function () { const playerSize = [300, 300]; bid.mediaTypes = {}; - bid.params.traffic = VIDEO; bid.mediaTypes[VIDEO] = { playerSize }; @@ -74,7 +77,7 @@ describe('BoldwinBidAdapter', function () { expect(data).to.be.an('object'); let placement = data['placements'][0]; expect(placement).to.be.an('object'); - expect(placement.traffic).to.equal(VIDEO); + expect(placement.adFormat).to.equal(VIDEO); expect(placement.wPlayer).to.equal(playerSize[0]); expect(placement.hPlayer).to.equal(playerSize[1]); }); @@ -127,7 +130,7 @@ describe('BoldwinBidAdapter', function () { expect(bannerResponses).to.be.an('array').that.is.not.empty; let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.4); expect(dataItem.width).to.equal(300); @@ -137,7 +140,9 @@ describe('BoldwinBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); + it('Should interpret video response', function () { const video = { body: [{ @@ -157,7 +162,7 @@ describe('BoldwinBidAdapter', function () { let dataItem = videoResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.5); expect(dataItem.vastUrl).to.equal('test.com'); @@ -165,6 +170,7 @@ describe('BoldwinBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret native response', function () { const native = { @@ -188,7 +194,7 @@ describe('BoldwinBidAdapter', function () { expect(nativeResponses).to.be.an('array').that.is.not.empty; let dataItem = nativeResponses[0]; - expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.4); @@ -201,6 +207,7 @@ describe('BoldwinBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should return an empty array if invalid banner response is passed', function () { const invBanner = { @@ -275,7 +282,7 @@ describe('BoldwinBidAdapter', function () { expect(userSync[0].type).to.exist; expect(userSync[0].url).to.exist; expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://cs.videowalldirect.com/?c=o&m=cookie'); + expect(userSync[0].url).to.be.equal('https://cs.videowalldirect.com'); }); }); }); diff --git a/test/spec/modules/brandmetricsRtdProvider_spec.js b/test/spec/modules/brandmetricsRtdProvider_spec.js new file mode 100644 index 00000000000..3cac5a3d559 --- /dev/null +++ b/test/spec/modules/brandmetricsRtdProvider_spec.js @@ -0,0 +1,191 @@ +import * as brandmetricsRTD from '../../../modules/brandmetricsRtdProvider.js'; +import {config} from 'src/config.js'; + +const VALID_CONFIG = { + name: 'brandmetrics', + waitForIt: true, + params: { + scriptId: '00000000-0000-0000-0000-000000000000', + bidders: ['ozone'] + } +}; + +const NO_BIDDERS_CONFIG = { + name: 'brandmetrics', + waitForIt: true, + params: { + scriptId: '00000000-0000-0000-0000-000000000000' + } +}; + +const NO_SCRIPTID_CONFIG = { + name: 'brandmetrics', + waitForIt: true +}; + +const USER_CONSENT = { + gdpr: { + vendorData: { + vendor: { + consents: { + 422: true + } + }, + purpose: { + consents: { + 1: true, + 7: true + } + } + }, + gdprApplies: true + } +}; + +const NO_TCF_CONSENT = { + gdpr: { + vendorData: { + vendor: { + consents: { + 422: false + } + }, + purpose: { + consents: { + 1: false, + 7: false + } + } + }, + gdprApplies: true + } +}; + +const NO_USP_CONSENT = { + usp: '1NYY' +}; + +function mockSurveyLoaded(surveyConf) { + const commands = window._brandmetrics || []; + commands.forEach(command => { + if (command.cmd === '_addeventlistener') { + const conf = command.val; + if (conf.event === 'surveyloaded') { + conf.handler(surveyConf); + } + } + }); +} + +function scriptTagExists(url) { + const tags = document.getElementsByTagName('script'); + for (let i = 0; i < tags.length; i++) { + if (tags[i].src === url) { + return true; + } + } + return false; +} + +describe('BrandmetricsRTD module', () => { + beforeEach(function () { + const scriptTags = document.getElementsByTagName('script'); + for (let i = 0; i < scriptTags.length; i++) { + if (scriptTags[i].src.indexOf('brandmetrics') !== -1) { + scriptTags[i].remove(); + } + } + }); + + it('should init and return true', () => { + expect(brandmetricsRTD.brandmetricsSubmodule.init(VALID_CONFIG, USER_CONSENT)).to.equal(true); + }); + + it('should init and return true even if bidders is not included', () => { + expect(brandmetricsRTD.brandmetricsSubmodule.init(NO_BIDDERS_CONFIG, USER_CONSENT)).to.equal(true); + }); + + it('should init even if script- id is not configured', () => { + expect(brandmetricsRTD.brandmetricsSubmodule.init(NO_SCRIPTID_CONFIG, USER_CONSENT)).to.equal(true); + }); + + it('should not init when there is no TCF- consent', () => { + expect(brandmetricsRTD.brandmetricsSubmodule.init(VALID_CONFIG, NO_TCF_CONSENT)).to.equal(false); + }); + + it('should not init when there is no usp- consent', () => { + expect(brandmetricsRTD.brandmetricsSubmodule.init(VALID_CONFIG, NO_USP_CONSENT)).to.equal(false); + }); +}); + +describe('getBidRequestData', () => { + beforeEach(function () { + config.resetConfig() + }) + + it('should set targeting keys for specified bidders', () => { + brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => { + const bidderConfig = config.getBidderConfig() + const expected = VALID_CONFIG.params.bidders + + expected.forEach(exp => { + expect(bidderConfig[exp].ortb2.user.ext.data.mockTargetKey).to.equal('mockMeasurementId') + }) + }, VALID_CONFIG); + + mockSurveyLoaded({ + available: true, + conf: { + displayOption: { + type: 'pbjs', + targetKey: 'mockTargetKey' + } + }, + survey: { + measurementId: 'mockMeasurementId' + } + }); + }); + + it('should only set targeting keys when the brandmetrics survey- type is "pbjs"', () => { + mockSurveyLoaded({ + available: true, + conf: { + displayOption: { + type: 'dfp', + targetKey: 'mockTargetKey' + } + }, + survey: { + measurementId: 'mockMeasurementId' + } + }); + + brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => {}, VALID_CONFIG); + const bidderConfig = config.getBidderConfig() + expect(Object.keys(bidderConfig).length).to.equal(0) + }); + + it('should use a default targeting key name if the brandmetrics- configuration does not include one', () => { + mockSurveyLoaded({ + available: true, + conf: { + displayOption: { + type: 'pbjs', + } + }, + survey: { + measurementId: 'mockMeasurementId' + } + }); + + brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => {}, VALID_CONFIG); + + const bidderConfig = config.getBidderConfig() + const expected = VALID_CONFIG.params.bidders + + expected.forEach(exp => { + expect(bidderConfig[exp].ortb2.user.ext.data.brandmetrics_survey).to.equal('mockMeasurementId') + }) + }); +}); diff --git a/test/spec/modules/braveBidAdapter_spec.js b/test/spec/modules/braveBidAdapter_spec.js new file mode 100644 index 00000000000..392f3b9f263 --- /dev/null +++ b/test/spec/modules/braveBidAdapter_spec.js @@ -0,0 +1,363 @@ +import { expect } from 'chai'; +import { spec } from 'modules/braveBidAdapter.js'; + +const request_native = { + code: 'brave-native-prebid', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bidder: 'brave', + params: { + placementId: 'to0QI2aPgkbBZq6vgf0oHitouZduz0qw' + } +}; + +const request_banner = { + code: 'brave-prebid', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bidder: 'brave', + params: { + placementId: 'to0QI2aPgkbBZq6vgf0oHitouZduz0qw' + } +} + +const bidRequest = { + gdprConsent: { + consentString: 'HFIDUYFIUYIUYWIPOI87392DSU', + gdprApplies: true + }, + uspConsent: 'uspConsentString', + bidderRequestId: 'testid', + refererInfo: { + referer: 'testdomain.com' + }, + timeout: 700 +} + +const request_video = { + code: 'brave-video-prebid', + mediaTypes: { video: { + minduration: 1, + maxduration: 999, + boxingallowed: 1, + skip: 0, + mimes: [ + 'application/javascript', + 'video/mp4' + ], + playerSize: [[768, 1024]], + protocols: [ + 2, 3 + ], + linearity: 1, + api: [ + 1, + 2 + ] + } + }, + + bidder: 'brave', + params: { + placementId: 'to0QI2aPgkbBZq6vgf0oHitouZduz0qw' + } + +} + +const response_banner = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: 'admcode', + crid: 'crid', + ext: { + mediaType: 'banner' + } + }] + }] +}; + +const response_video = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: 'admcode', + crid: 'crid', + ext: { + mediaType: 'video' + } + }], + }], +}; + +let imgData = { + url: `https://example.com/image`, + w: 1200, + h: 627 +}; + +const response_native = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: { native: + { + assets: [ + {id: 1, title: 'dummyText'}, + {id: 3, image: imgData}, + { + id: 5, + data: {value: 'organization.name'} + } + ], + link: {url: 'example.com'}, + imptrackers: ['tracker1.com', 'tracker2.com', 'tracker3.com'], + jstracker: 'tracker1.com' + } + }, + crid: 'crid', + ext: { + mediaType: 'native' + } + }], + }], +}; + +describe('BraveBidAdapter', function() { + describe('isBidRequestValid', function() { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(request_banner)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, request_banner); + bid.params = { + 'IncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('build Native Request', function () { + const request = spec.buildRequests([request_native], bidRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://point.bravegroup.tv/?t=2&partner=to0QI2aPgkbBZq6vgf0oHitouZduz0qw'); + }); + + it('Returns empty data if no valid requests are passed', function () { + let serverRequest = spec.buildRequests([]); + expect(serverRequest).to.be.an('array').that.is.empty; + }); + }); + + describe('build Banner Request', function () { + const request = spec.buildRequests([request_banner], bidRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://point.bravegroup.tv/?t=2&partner=to0QI2aPgkbBZq6vgf0oHitouZduz0qw'); + }); + }); + + describe('build Video Request', function () { + const request = spec.buildRequests([request_video], bidRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://point.bravegroup.tv/?t=2&partner=to0QI2aPgkbBZq6vgf0oHitouZduz0qw'); + }); + }); + + describe('interpretResponse', function () { + it('Empty response must return empty array', function() { + const emptyResponse = null; + let response = spec.interpretResponse(emptyResponse); + + expect(response).to.be.an('array').that.is.empty; + }) + + it('Should interpret banner response', function () { + const bannerResponse = { + body: response_banner + } + + const expectedBidResponse = { + requestId: response_banner.seatbid[0].bid[0].impid, + cpm: response_banner.seatbid[0].bid[0].price, + width: response_banner.seatbid[0].bid[0].w, + height: response_banner.seatbid[0].bid[0].h, + ttl: response_banner.ttl || 1200, + currency: response_banner.cur || 'USD', + netRevenue: true, + creativeId: response_banner.seatbid[0].bid[0].crid, + dealId: response_banner.seatbid[0].bid[0].dealid, + mediaType: 'banner', + ad: response_banner.seatbid[0].bid[0].adm + } + + let bannerResponses = spec.interpretResponse(bannerResponse); + + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.ad).to.equal(expectedBidResponse.ad); + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret video response', function () { + const videoResponse = { + body: response_video + } + + const expectedBidResponse = { + requestId: response_video.seatbid[0].bid[0].impid, + cpm: response_video.seatbid[0].bid[0].price, + width: response_video.seatbid[0].bid[0].w, + height: response_video.seatbid[0].bid[0].h, + ttl: response_video.ttl || 1200, + currency: response_video.cur || 'USD', + netRevenue: true, + creativeId: response_video.seatbid[0].bid[0].crid, + dealId: response_video.seatbid[0].bid[0].dealid, + mediaType: 'video', + vastUrl: response_video.seatbid[0].bid[0].adm + } + + let videoResponses = spec.interpretResponse(videoResponse); + + expect(videoResponses).to.be.an('array').that.is.not.empty; + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.vastUrl).to.equal(expectedBidResponse.vastUrl) + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret native response', function () { + const nativeResponse = { + body: response_native + } + + const expectedBidResponse = { + requestId: response_native.seatbid[0].bid[0].impid, + cpm: response_native.seatbid[0].bid[0].price, + width: response_native.seatbid[0].bid[0].w, + height: response_native.seatbid[0].bid[0].h, + ttl: response_native.ttl || 1200, + currency: response_native.cur || 'USD', + netRevenue: true, + creativeId: response_native.seatbid[0].bid[0].crid, + dealId: response_native.seatbid[0].bid[0].dealid, + mediaType: 'native', + native: {clickUrl: response_native.seatbid[0].bid[0].adm.native.link.url} + } + + let nativeResponses = spec.interpretResponse(nativeResponse); + + expect(nativeResponses).to.be.an('array').that.is.not.empty; + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'native', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.native.clickUrl).to.equal(expectedBidResponse.native.clickUrl) + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + }); +}) diff --git a/test/spec/modules/bridgewellBidAdapter_spec.js b/test/spec/modules/bridgewellBidAdapter_spec.js index 0860404355e..8da82c71820 100644 --- a/test/spec/modules/bridgewellBidAdapter_spec.js +++ b/test/spec/modules/bridgewellBidAdapter_spec.js @@ -2,6 +2,14 @@ import { expect } from 'chai'; import { spec } from 'modules/bridgewellBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +const userId = { + 'criteoId': 'vYlICF9oREZlTHBGRVdrJTJCUUJnc3U2ckNVaXhrV1JWVUZVSUxzZmJlcnJZR0ZxbVhFRnU5bDAlMkJaUWwxWTlNcmdEeHFrJTJGajBWVlV4T3lFQ0FyRVcxNyUyQlIxa0lLSlFhcWJpTm9PSkdPVkx0JTJCbzlQRTQlM0Q', + 'pubcid': '074864cb-3705-430e-9ff7-48ccf3c21b94', + 'sharedid': {'id': '01F61MX53D786DSB2WYD38ZVM7', 'third': '01F61MX53D786DSB2WYD38ZVM7'}, + 'uid2': {'id': 'eb33b0cb-8d35-1234-b9c0-1a31d4064777'}, + 'flocId': {'id': '12345', 'version': 'chrome.1.1'}, +} + describe('bridgewellBidAdapter', function () { const adapter = newBidder(spec); @@ -87,6 +95,7 @@ describe('bridgewellBidAdapter', function () { 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', + 'userId': userId, }, { 'bidder': 'bridgewell', @@ -126,6 +135,7 @@ describe('bridgewellBidAdapter', function () { 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', + 'userId': userId, } ]; @@ -142,10 +152,13 @@ describe('bridgewellBidAdapter', function () { expect(payload.adUnits).to.be.an('array'); expect(payload.url).to.exist.and.to.equal('https://www.bridgewell.com/'); for (let i = 0, max_i = payload.adUnits.length; i < max_i; i++) { - expect(payload.adUnits[i]).to.have.property('ChannelID').that.is.a('string'); - expect(payload.adUnits[i]).to.not.have.property('cid'); - expect(payload.adUnits[i]).to.have.property('adUnitCode').and.to.equal('adunit-code-2'); - expect(payload.adUnits[i]).to.have.property('requestId').and.to.equal('3150ccb55da321'); + let u = payload.adUnits[i]; + expect(u).to.have.property('ChannelID').that.is.a('string'); + expect(u).to.not.have.property('cid'); + expect(u).to.have.property('adUnitCode').and.to.equal('adunit-code-2'); + expect(u).to.have.property('requestId').and.to.equal('3150ccb55da321'); + expect(u).to.have.property('userIds'); + expect(u.userIds).to.deep.equal(userId); } }); @@ -170,6 +183,7 @@ describe('bridgewellBidAdapter', function () { 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', + 'userId': userId }, ]; @@ -180,10 +194,13 @@ describe('bridgewellBidAdapter', function () { expect(payload.adUnits).to.be.an('array'); expect(payload.url).to.exist.and.to.equal('https://www.bridgewell.com/'); for (let i = 0, max_i = payload.adUnits.length; i < max_i; i++) { - expect(payload.adUnits[i]).to.have.property('cid').that.is.a('number'); - expect(payload.adUnits[i]).to.not.have.property('ChannelID'); - expect(payload.adUnits[i]).to.have.property('adUnitCode').and.to.equal('adunit-code-2'); - expect(payload.adUnits[i]).to.have.property('requestId').and.to.equal('3150ccb55da321'); + let u = payload.adUnits[i]; + expect(u).to.have.property('cid').that.is.a('number'); + expect(u).to.not.have.property('ChannelID'); + expect(u).to.have.property('adUnitCode').and.to.equal('adunit-code-2'); + expect(u).to.have.property('requestId').and.to.equal('3150ccb55da321'); + expect(u).to.have.property('userIds'); + expect(u.userIds).to.deep.equal(userId); } }); diff --git a/test/spec/modules/brightMountainMediaBidAdapter_spec.js b/test/spec/modules/brightMountainMediaBidAdapter_spec.js index 6c7ef816f4f..5e433abebd8 100644 --- a/test/spec/modules/brightMountainMediaBidAdapter_spec.js +++ b/test/spec/modules/brightMountainMediaBidAdapter_spec.js @@ -2,8 +2,6 @@ import { expect } from 'chai'; import { spec } from '../../../modules/brightMountainMediaBidAdapter.js'; const BIDDER_CODE = 'bmtm'; -const ENDPOINT_URL = 'https://one.elitebidder.com/api/hb'; -const ENDPOINT_URL_SYNC = 'https://console.brightmountainmedia.com:8443/cookieSync'; const PLACEMENT_ID = 329; describe('brightMountainMediaBidAdapter_spec', function () { @@ -22,6 +20,47 @@ describe('brightMountainMediaBidAdapter_spec', function () { } }, transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + getFloor: function () { + return { + currency: 'USD', + floor: 0.5, + } + }, + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1 + } + ] + }, + userIdAsEids: [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'ID5-ZHMOaW5vh_TJhKVSaTWmuoTpwqjGGwx5v0WbaSV8yw', + 'atype': 1, + 'ext': { + 'linkType': 2 + } + } + ] + }, + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '00000000000000000000000000', + 'atype': 1 + } + ] + } + ] }; let bidVideo = { @@ -56,7 +95,12 @@ describe('brightMountainMediaBidAdapter_spec', function () { refererInfo: { referer: 'http://www.example.com', reachedTop: true, - } + }, + gdprConsent: { + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + gdprApplies: true + }, + bids: [bidBanner], }; describe('isBidRequestValid', function () { @@ -64,61 +108,160 @@ describe('brightMountainMediaBidAdapter_spec', function () { expect(spec.isBidRequestValid(bidBanner)).to.be.true; }); it('Should return false when required params are not passed', function () { - bidBanner.params = {} + bidBanner.params = {}; expect(spec.isBidRequestValid(bidBanner)).to.be.false; }); }); - function testServerRequestBody(serverRequest) { + describe('buildRequests', function () { + let request = spec.buildRequests([bidBanner], bidderRequest)[0]; + let data = JSON.parse(request.data); + it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequest).to.exist; - expect(serverRequest.method).to.exist; - expect(serverRequest.url).to.exist; - expect(serverRequest.data).to.exist; + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + expect(request.method).to.be.a('string'); + expect(request.url).to.be.a('string'); + expect(request.data).to.be.an('string'); + }); + + it('Returns valid data if array of bids is valid', function () { + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('at', 'site', 'device', 'cur', 'tmax', 'regs', 'user', 'source', 'imp', 'id'); + expect(data.at).to.be.a('number'); + expect(data.site).to.be.an('object'); + expect(data.device).to.be.an('object'); + expect(data.cur).to.be.an('array'); + expect(data.tmax).to.be.a('number'); + expect(data.regs).to.be.an('object'); + expect(data.user).to.be.an('object'); + expect(data.source).to.be.an('object'); + expect(data.imp).to.be.an('array'); + expect(data.id).to.be.a('string'); + }); + + it('Sends bidfloor param if present', function () { + expect(data.imp[0].bidfloor).to.equal(0.5); }); - it('Returns POST method', function () { - expect(serverRequest.method).to.equal('POST'); + + it('Sends regs info if exists', function () { + expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); + expect(data.regs.ext.gdprConsentString).to.exist.and.to.be.a('string'); + expect(data.regs.ext.us_privacy).to.exist.and.to.be.a('string'); }); - it('Returns valid URL', function () { - expect(serverRequest.url).to.equal(ENDPOINT_URL); + + it('Sends schain info if exists', function () { + expect(data.source.ext).to.be.an('object'); }); - it('Returns valid data if array of bids is valid', function () { - let data = serverRequest.data; + it('sends userId info if exists', function () { + expect(data.user.ext).to.have.property('eids'); + expect(data.user.ext.eids).to.not.equal(null).and.to.not.be.undefined; + expect(data.user.ext.eids.length).to.greaterThan(0); + for (let index in data.user.ext.eids) { + let eid = data.user.ext.eids[index]; + expect(eid.source).to.not.equal(null).and.to.not.be.undefined; + expect(eid.uids).to.not.equal(null).and.to.not.be.undefined; + for (let uidsIndex in eid.uids) { + let uid = eid.uids[uidsIndex]; + expect(uid.id).to.not.equal(null).and.to.not.be.undefined; + } + } + }); + + it('Returns valid data if array of bids is valid for banner', function () { expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.language).to.be.a('string'); - expect(data.secure).to.be.within(0, 1); - expect(data.host).to.be.a('string'); - expect(data.page).to.be.a('string'); - let placements = data['placements']; - expect(placements).to.be.an('array'); + expect(data).to.have.property('imp'); + expect(data.imp.length).to.greaterThan(0); + expect(data.imp[0]).to.have.property('banner'); + expect(data.imp[0].banner).to.be.an('object'); + expect(data.imp[0].banner.h).to.exist.and.to.be.a('number'); + expect(data.imp[0].banner.w).to.exist.and.to.be.a('number'); }); - } - describe('buildRequests', function () { - bidderRequest['bids'] = [bidBanner]; - let serverRequest = spec.buildRequests([bidBanner], bidderRequest); - testServerRequestBody(serverRequest); - - bidderRequest['bids'] = [bidVideo]; - serverRequest = spec.buildRequests([bidVideo], bidderRequest); - testServerRequestBody(serverRequest); - - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; + it('Returns valid data if array of bids is valid for video', function () { + bidderRequest.bids = [bidVideo]; + let serverRequest = spec.buildRequests([bidVideo], bidderRequest)[0]; + let data = JSON.parse(serverRequest.data); + expect(data).to.be.an('object'); + expect(data).to.have.property('imp'); + expect(data.imp.length).to.greaterThan(0); + expect(data.imp[0]).to.have.property('video'); + expect(data.imp[0].video).to.be.an('object'); + expect(data.imp[0].video.h).to.exist.and.to.be.a('number'); + expect(data.imp[0].video.w).to.exist.and.to.be.a('number'); }); }); - function testServerResponse(serverResponses) { - it('Returns an array of valid server responses if response object is valid', function () { - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - let dataItem = serverResponses[i]; + describe('interpretResponse', function () { + let resObjectBanner = { + 'id': '2763-05f22da29b3ffb6-6959', + 'bidid': 'e5b41111bec9e4a4e94b85d082f8fb08', + 'seatbid': [ + { + 'bid': [ + { + 'id': '9550c3e641761cfbf2a4dd672b50ddb9', + 'impid': '968', + 'price': 0.3, + 'w': 300, + 'h': 250, + 'nurl': 'https://example.com/?w=nr&pf=${AUCTION_PRICE}&type=b&uq=483531c101942cbb270cd088b2eec43f', + 'adomain': [ + 'example.com' + ], + 'cid': '3845_105654', + 'crid': '3845_305654', + 'adm': '

Test Ad

', + 'adid': '17794c46ca26', + 'iurl': 'https://example.com/?t=preview2&k=17794c46ca26' + } + ], + 'seat': '3845' + } + ], + 'cur': 'USD' + }; + + let resObjectVideo = { + 'id': '2763-05f22da29b3ffb6-6959', + 'bidid': 'e5b41111bec9e4a4e94b85d082f8fb08', + 'seatbid': [ + { + 'bid': [ + { + 'id': '9550c3e641761cfbf2a4dd672b50ddb9', + 'impid': '968', + 'price': 0.3, + 'w': 300, + 'h': 250, + 'nurl': 'https://example.com/?w=nr&pf=${AUCTION_PRICE}&type=b&uq=483531c101942cbb270cd088b2eec43f', + 'adomain': [ + 'example.com' + ], + 'cid': '3845_105654', + 'crid': '3845_305654', + 'adm': '', + 'adid': '17794c46ca26', + 'iurl': 'https://example.com/?t=preview2&k=17794c46ca26' + } + ], + 'seat': '3845' + } + ], + 'cur': 'USD' + }; + + it('Returns an array of valid response if response object is valid for banner', function () { + const bidResponse = spec.interpretResponse({ + body: resObjectBanner + }, { bidRequest: bidBanner }); + + expect(bidResponse).to.be.an('array').that.is.not.empty; + for (let i = 0; i < bidResponse.length; i++) { + let dataItem = bidResponse[i]; expect(dataItem.requestId).to.be.a('string'); expect(dataItem.cpm).to.be.a('number'); expect(dataItem.width).to.be.a('number'); @@ -129,49 +272,37 @@ describe('brightMountainMediaBidAdapter_spec', function () { expect(dataItem.netRevenue).to.be.a('boolean'); expect(dataItem.currency).to.be.a('string'); expect(dataItem.mediaType).to.be.a('string'); + expect(dataItem.meta.advertiserDomains[0]).to.be.a('string'); } }); - } - - describe('interpretResponse', function () { - let resObjectBanner = { - body: [{ - requestId: '123', - mediaType: 'banner', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD' - }] - }; - let resObjectVideo = { - body: [{ - requestId: '123', - mediaType: 'video', - cpm: 1.5, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD' - }] - }; - let serverResponses = spec.interpretResponse(resObjectBanner); - testServerResponse(serverResponses); + it('Returns an array of valid response if response object is valid for video', function () { + const bidResponse = spec.interpretResponse({ + body: resObjectVideo + }, { bidRequest: bidVideo }); - serverResponses = spec.interpretResponse(resObjectVideo); - testServerResponse(serverResponses); + expect(bidResponse).to.be.an('array').that.is.not.empty; + for (let i = 0; i < bidResponse.length; i++) { + let dataItem = bidResponse[i]; + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.vastXml).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + expect(dataItem.meta.advertiserDomains[0]).to.be.a('string'); + } + }); it('Returns an empty array if invalid response is passed', function () { - serverResponses = spec.interpretResponse('invalid_response'); - expect(serverResponses).to.be.an('array').that.is.empty; + const bidResponse = spec.interpretResponse({ + body: '' + }, { bidRequest: bidBanner }); + expect(bidResponse).to.be.an('array').that.is.empty; }); }); @@ -184,7 +315,7 @@ describe('brightMountainMediaBidAdapter_spec', function () { expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.exist; expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.exist; expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.equal('iframe') - expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal(ENDPOINT_URL_SYNC) + expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal('https://console.brightmountainmedia.com:8443/cookieSync') }); }); }); diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js index a89391d681e..b7d52c9f7d5 100644 --- a/test/spec/modules/brightcomBidAdapter_spec.js +++ b/test/spec/modules/brightcomBidAdapter_spec.js @@ -247,7 +247,8 @@ describe('brightcomBidAdapter', function() { 'nurl': '', 'adm': '', 'w': 300, - 'h': 250 + 'h': 250, + 'adomain': ['example.com'] }] }] } @@ -265,7 +266,10 @@ describe('brightcomBidAdapter', function() { 'netRevenue': true, 'mediaType': 'banner', 'ad': `
`, - 'ttl': 60 + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } }]; let result = spec.interpretResponse(response); @@ -283,7 +287,10 @@ describe('brightcomBidAdapter', function() { 'netRevenue': true, 'mediaType': 'banner', 'ad': `
`, - 'ttl': 60 + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } }]; let result = spec.interpretResponse(response); diff --git a/test/spec/modules/browsiRtdProvider_spec.js b/test/spec/modules/browsiRtdProvider_spec.js index ee37d16905b..32e1c7fe795 100644 --- a/test/spec/modules/browsiRtdProvider_spec.js +++ b/test/spec/modules/browsiRtdProvider_spec.js @@ -1,5 +1,6 @@ import * as browsiRTD from '../../../modules/browsiRtdProvider.js'; import {makeSlot} from '../integration/faker/googletag.js'; +import * as utils from '../../../src/utils' describe('browsi Real time data sub module', function () { const conf = { @@ -29,11 +30,11 @@ describe('browsi Real time data sub module', function () { }); it('should match placement with ad unit', function () { - const slot = makeSlot({code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1'}); + const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); - const test1 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_300x250']); // true - const test2 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_300x250', '/57778053/Browsi']); // true - const test3 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_Low']); // false + const test1 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc']); // true + const test2 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc', '/456/def']); // true + const test3 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/def']); // false const test4 = browsiRTD.isIdMatchingAdUnit(slot, []); // true expect(test1).to.equal(true); @@ -43,12 +44,12 @@ describe('browsi Real time data sub module', function () { }); it('should return correct macro values', function () { - const slot = makeSlot({code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1'}); + const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); slot.setTargeting('test', ['test', 'value']); // slot getTargeting doesn't act like GPT so we can't expect real value const macroResult = browsiRTD.getMacroId({p: '/'}, slot); - expect(macroResult).to.equal('/57778053/Browsi_Demo_300x250/NA'); + expect(macroResult).to.equal('/123/abc/NA'); const macroResultB = browsiRTD.getMacroId({}, slot); expect(macroResultB).to.equal('browsiAd_1'); @@ -72,7 +73,7 @@ describe('browsi Real time data sub module', function () { it('should return prediction from server', function () { makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); const data = { - p: {'hasPrediction': {p: 0.234}}, + p: {'hasPrediction': {ps: {0: 0.234}}}, kn: 'bv', pmd: undefined }; @@ -80,4 +81,58 @@ describe('browsi Real time data sub module', function () { expect(browsiRTD.browsiSubmodule.getTargetingData(['hasPrediction'])).to.eql({hasPrediction: {bv: '0.20'}}); }) }) + + describe('should return matching prediction', function () { + const predictions = { + 0: 0.123, + 1: 0.254, + 3: 0, + 4: 0.8 + } + const singlePrediction = { + 0: 0.123 + } + it('should return raw value if valid', function () { + expect(browsiRTD.getCurrentData(predictions, 0)).to.equal(0.123); + expect(browsiRTD.getCurrentData(predictions, 1)).to.equal(0.254); + }) + it('should return 0 for prediction = 0', function () { + expect(browsiRTD.getCurrentData(predictions, 3)).to.equal(0); + }) + it('should return -1 for invalid params', function () { + expect(browsiRTD.getCurrentData(null, 3)).to.equal(-1); + expect(browsiRTD.getCurrentData(predictions, null)).to.equal(-1); + }) + it('should return prediction according to object keys length ', function () { + expect(browsiRTD.getCurrentData(singlePrediction, 0)).to.equal(0.123); + expect(browsiRTD.getCurrentData(singlePrediction, 1)).to.equal(-1); + expect(browsiRTD.getCurrentData(singlePrediction, 2)).to.equal(-1); + expect(browsiRTD.getCurrentData(predictions, 4)).to.equal(0.8); + expect(browsiRTD.getCurrentData(predictions, 5)).to.equal(0.8); + expect(browsiRTD.getCurrentData(predictions, 8)).to.equal(0.8); + }) + }) + describe('should set bid request data', function () { + const data = { + p: { + 'adUnit1': {ps: {0: 0.234}}, + 'adUnit2': {ps: {0: 0.134}}}, + kn: 'bv', + pmd: undefined + }; + browsiRTD.setData(data); + const fakeAdUnits = [ + { + code: 'adUnit1' + }, + { + code: 'adUnit2' + } + ] + browsiRTD.browsiSubmodule.getBidRequestData({adUnits: fakeAdUnits}, () => {}, {}, null); + it('should set ad unit params with prediction values', function () { + expect(utils.deepAccess(fakeAdUnits[0], 'ortb2Imp.ext.data.browsi')).to.eql({bv: '0.20'}); + expect(utils.deepAccess(fakeAdUnits[1], 'ortb2Imp.ext.data.browsi')).to.eql({bv: '0.10'}); + }) + }) }); diff --git a/test/spec/modules/bucksenseBidAdapter_spec.js b/test/spec/modules/bucksenseBidAdapter_spec.js index f49a63d2003..b977c3a9dd1 100644 --- a/test/spec/modules/bucksenseBidAdapter_spec.js +++ b/test/spec/modules/bucksenseBidAdapter_spec.js @@ -128,7 +128,10 @@ describe('Bucksense Adapter', function() { 'creativeId': 'creative002', 'currency': 'USD', 'netRevenue': false, - 'ad': '
' + 'ad': '
', + 'meta': { + 'advertiserDomains': ['http://www.bucksense.com/'] + } } }; }); diff --git a/test/spec/modules/buzzoolaBidAdapter_spec.js b/test/spec/modules/buzzoolaBidAdapter_spec.js index 8a04999219d..312441c4202 100644 --- a/test/spec/modules/buzzoolaBidAdapter_spec.js +++ b/test/spec/modules/buzzoolaBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/buzzoolaBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import '../../../src/prebid.js'; import {executeRenderer, Renderer} from '../../../src/Renderer.js'; import {deepClone} from '../../../src/utils.js'; @@ -54,7 +55,12 @@ const BANNER_RESPONSE = [{ 'netRevenue': true, 'ttl': 10800, 'ad': '
', - 'mediaType': 'banner' + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': [ + 'buzzoola.com' + ] + } }]; const REQUIRED_BANNER_FIELDS = [ @@ -67,7 +73,8 @@ const REQUIRED_BANNER_FIELDS = [ 'creativeId', 'netRevenue', 'currency', - 'mediaType' + 'mediaType', + 'meta' ]; const VIDEO_BID = { @@ -92,17 +99,22 @@ const VIDEO_BID_REQUEST = { const VIDEO_RESPONSE = [{ 'requestId': '325a54271dc40a', - 'cpm': 4.6158956756756755, + 'cpm': 5.528554074074074, 'width': 640, - 'height': 380, + 'height': 480, 'creativeId': '11774', 'dealId': '', 'currency': 'RUB', 'netRevenue': true, 'ttl': 10800, 'ad': '{"crs":[{"advertiser_id":165,"title":"qa//PrebidJStestVideoURL","description":"qa//PrebidJStest","duration":0,"ya_id":"55038886","raw_content":"{\\"main_content\\": \\"https://tube.buzzoola.com/xstatic/o42/mcaug/2.mp4\\"}","content":{"main_content":"https://tube.buzzoola.com/xstatic/o42/mcaug/2.mp4"},"content_type":"video_url","sponsor_link":"","sponsor_name":"","overlay":"","overlay_start_after":0,"overlay_close_after":0,"action_button_title":"","tracking_url":{},"iframe_domains":[],"soc_share_url":"https://tube.buzzoola.com/share.html","player_show_skip_button_before_play":false,"player_show_skip_button_seconds":5,"player_show_title":true,"player_data_attributes":{"expandable":"default","overroll":"default"},"click_event_view":"default","share_panel_position":"left","auto_play":true,"logo_url":{},"share_buttons":["vkontakte","facebook","twitter","moimir","odnoklassniki","embed"],"player_show_panels":false,"thumbnail":"","tracking_js":{},"click_event_url":"https://exchange.buzzoola.com/event/f9382ceb-49c2-4683-50d8-5c516c53cd69/14795a96-6261-49dc-7241-207333ab1490/m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpIERGosa1adogXgqjDml4Pm/click/0/","vpaid_js_url":"https://tube.buzzoola.com/new/js/lib/vpaid_js_proxy.js","skip_clickthru":false,"landing_link_text":"","sound_enabled_by_default":false,"landing_link_position":"right","displayed_price":"","js_wrapper_url":"","enable_moat":false,"branding_template":"","event_url":"https://exchange.buzzoola.com/event/f9382ceb-49c2-4683-50d8-5c516c53cd69/14795a96-6261-49dc-7241-207333ab1490/m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpIERGosa1adogXgqjDml4Pm/","resend_event_url":"https://exchange.buzzoola.com/resend_event/f9382ceb-49c2-4683-50d8-5c516c53cd69/14795a96-6261-49dc-7241-207333ab1490/m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpIERGosa1adogXgqjDml4Pm/","creative_hash":"m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpIERGosa1adogXgqjDml4Pm","custom_html":"","custom_js":"","height":0,"width":0,"campaign_id":5758,"line_item_id":17319,"creative_id":11774,"extra":{"imp_id":"14795a96-6261-49dc-7241-207333ab1490","rtime":"2019-08-27 13:58:36"},"subcontent":"vast","auction_settings":{"price":"4.6158956756756755","currency":"RUB","event_name":"player_seen","time_slice":0},"hash_to_embed":"kbDH64c7yFYkSu0KCwSkoUD2bNHAnUTHBERqLGtWnaIF4Kow5peD5g","need_ad":false}],"tracking_urls":{"ctor":["https://www.tns-counter.ru/V13a****buzzola_com/ru/CP1251/tmsec=buzzola_total/1322650417245790778","https://www.tns-counter.ru/V13a****buzzoola_kz/ru/UTF-8/tmsec=buzzoola_video/5395765100939533275","https://buzzoolaru.solution.weborama.fr/fcgi-bin/dispatch.fcgi?a.A=ev&a.si=3071&a.te=37&a.aap=1&a.agi=862&a.evn=PrebidJS.test&g.ra=4581478478720298652","https://x01.aidata.io/0.gif?pid=BUZZOOLA&id=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://top-fwz1.mail.ru/counter?id=3026769","https://www.tns-counter.ru/V13a****buzzola_com/ru/UTF-8/tmsec=buzzola_inread/542059452789128996","https://dm.hybrid.ai/match?id=111&vid=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://px.adhigh.net/p/cm/buzzoola?u=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://ssp1.rtb.beeline.ru/userbind?src=buz&ssp_user_id=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://sync.upravel.com/image?source=buzzoola&id=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://relap.io/api/partners/bzcs.gif?uid=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://x.bidswitch.net/sync?ssp=sspicyads","https://inv-nets.admixer.net/adxcm.aspx?ssp=3C5173FC-CA30-4692-9116-009C19CB1BF9&rurl=%2F%2Fexchange.buzzoola.com%2Fcookiesync%2Fdsp%2Fadmixer-video%2F%24%24visitor_cookie%24%24","https://sync.datamind.ru/cookie/accepter?source=buzzoola&id=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://dmp.vihub.ru/match?sysid=buz&redir=no&uid=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://ad.adriver.ru/cgi-bin/rle.cgi?sid=1&ad=608223&bt=21&pid=2551979&bid=6150299&bn=6150299&rnd=1279444531737367663","https://reichelcormier.bid/point/?method=match&type=ssp&key=4677290772f9000878093d69c199bfba&id=3509&extUid=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://sync.republer.com/match?src=buzzoola&id=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://sm.rtb.mts.ru/p?id=dbdb5b13-e719-4987-7f6a-a882322bbfce&ssp=buzzoola","https://cm.mgid.com/m?cdsp=371151&adu=https%3A%2F%2Fexchange.buzzoola.com%2Fcookiesync%2Fdsp%2Fmarketgid-native%2F%7Bmuidn%7D","https://dmp.gotechnology.io/dmp/syncsspdmp?sspid=122258"]},"tracking_js":{"ctor":["https://buzzoola.fraudscore.mobi/dooJ9sheeeDaZ3fe.js?s=268671&l=417845"]},"placement":{"placement_id":417845,"unit_type":"inread","unit_settings":{"align":"left","autoplay_enable_sound":false,"creatives_amount":1,"debug_mode":false,"expandable":"never","sound_control":"default","target":"","width":"100%"},"unit_settings_list":["width","sound_control","debug_mode","target","creatives_amount","expandable","container_height","align","height"]},"uuid":"dbdb5b13-e719-4987-7f6a-a882322bbfce","auction_id":"f9382ceb-49c2-4683-50d8-5c516c53cd69","env":"prod"}', - 'vastXml': '\n00:00:30', - 'mediaType': 'video' + 'vastUrl': 'https://exchange.buzzoola.com/prebid/adm/6cfa2ee1-f001-4fab-5582-a62eaee46205/m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75R6wziBhL0JAERGosa1adogXgqjDml4Pm/?auction_id=92702ce1-2328-4c7a-57aa-41c738e8bb75', + 'mediaType': 'video', + 'meta': { + 'advertiserDomains': [ + 'buzzoola.com' + ] + } }]; const RENDERER_DATA = { @@ -121,8 +133,107 @@ const REQUIRED_VIDEO_FIELDS = [ 'creativeId', 'netRevenue', 'currency', - 'vastXml', - 'mediaType' + 'vastUrl', + 'mediaType', + 'meta' +]; + +const NATIVE_BID = { + 'bidder': 'buzzoola', + 'params': {'placementId': 417845}, + 'mediaTypes': { + 'native': { + 'image': { + 'required': true, + 'sizes': [640, 134] + }, + 'title': { + 'required': true, + 'len': 80 + }, + 'sponsoredBy': { + 'required': true + }, + 'clickUrl': { + 'required': true + }, + 'privacyLink': { + 'required': false + }, + 'body': { + 'required': true + }, + 'icon': { + 'required': true, + 'sizes': [50, 50] + } + } + }, + 'bidId': '22a42cd3522c6f' +}; + +const NATIVE_BID_REQUEST = { + bidderCode: 'buzzoola', + bids: [NATIVE_BID] +}; + +const NATIVE_RESPONSE = [{ + 'requestId': '22a42cd3522c6f', + 'cpm': 6.553015238095238, + 'width': 600, + 'height': 300, + 'creativeId': '17970', + 'dealId': '', + 'currency': 'RUB', + 'netRevenue': true, + 'ttl': 10800, + 'ad': 'https://tube.buzzoola.com/xstatic/o42/stoloto/6', + 'mediaType': 'native', + 'native': { + 'body': 'В 1388-м тираже «Русского лото» джекпот', + 'clickTrackers': [ + 'https://exchange.buzzoola.com/event/6cee890f-1878-4a37-46b3-0107b6c590ae/a1aedc5b-50f2-4a7c-6d24-e235bb1f87ed/m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpJwP8cy-zNH8GX-_nWFkILh/click/' + ], + 'clickUrl': 'https://ad.doubleclick.net/ddm/trackclk/N250204.3446512BUZZOOLA/B25801892.303578321;dc_trk_aid=496248119;dc_trk_cid=151207455;dc_lat=;dc_rdid=;tag_for_child_directed_treatment=;tfua=;ltd=?https://stoloto.onelink.me/mEJM?pid=Buzzoola_mb4&c=rl_10_05_2021&is_retargeting=true&af_ios_url=https%3A%2F%2Fapps.apple.com%2Fru%2Fapp%2F%25D1%2581%25D1%2582%25D0%25BE%25D0%25BB%25D0%25BE%25D1%2582%25D0%25BE-%25D1%2583-%25D0%25BD%25D0%25B0%25D1%2581-%25D0%25B2%25D1%258B%25D0%25B8%25D0%25B3%25D1%2580%25D1%258B%25D0%25B2%25D0%25B0%25D1%258E%25D1%2582%2Fid579961527&af_android_url=https%3A%2F%2Fgalaxystore.samsung.com%2Fdetail%2Fru.stoloto.mobile&af_dp=stolotoone%3A%2F%2Fgames&af_web_dp=https%3A%2F%2Fwww.stoloto.ru%2Fruslotto%2Fgame%3Flastdraw%3Fad%3Dbuzzoola_app_dx_rl_10_05_2021%26utm_source%3Dbuzzoola_app_dx%26utm_medium%3Dcpm%26utm_campaign%3Drl_10_05_2021%26utm_content%3Dbuzzoola_app_dx_mob_native_ios_mb4%26utm_term%3D__6ple2-9znjyg_', + 'icon': { + 'height': '100', + 'url': 'https://tube.buzzoola.com/xstatic/o42/stoloto/logo3.png', + 'width': '100' + }, + 'image': { + 'height': '450', + 'url': 'https://tube.buzzoola.com/xstatic/o42/stoloto/6/16x9.png', + 'width': '800' + }, + 'impressionTrackers': [ + 'https://exchange.buzzoola.com/event/6cee890f-1878-4a37-46b3-0107b6c590ae/a1aedc5b-50f2-4a7c-6d24-e235bb1f87ed/m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpJwP8cy-zNH8GX-_nWFkILh/ctor/', + 'https://exchange.buzzoola.com/event/6cee890f-1878-4a37-46b3-0107b6c590ae/a1aedc5b-50f2-4a7c-6d24-e235bb1f87ed/m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpJwP8cy-zNH8GX-_nWFkILh/impression/?price=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}', + 'https://exchange.buzzoola.com/event/6cee890f-1878-4a37-46b3-0107b6c590ae/a1aedc5b-50f2-4a7c-6d24-e235bb1f87ed/m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpJwP8cy-zNH8GX-_nWFkILh/player_seen/', + 'https://cr.frontend.weborama.fr/cr?key=mailru&url=https%3A%2F%2Fad.mail.ru%2Fcm.gif%3Fp%3D68%26id%3D%7BWEBO_CID%7D' + ], + 'sponsoredBy': 'Buzzoola', + 'title': 'Test PrebidJS Native' + }, + 'meta': { + 'advertiserDomains': [ + 'buzzoola.com' + ] + } +}]; + +const REQUIRED_NATIVE_FIELDS = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'ad', + 'native', + 'ttl', + 'creativeId', + 'netRevenue', + 'currency', + 'mediaType', + 'meta' ]; describe('buzzoolaBidAdapter', () => { @@ -149,18 +260,22 @@ describe('buzzoolaBidAdapter', () => { describe('buildRequests', () => { let videoBidRequests = [VIDEO_BID]; let bannerBidRequests = [BANNER_BID]; + let nativeBidRequests = [NATIVE_BID]; const bannerRequest = spec.buildRequests(bannerBidRequests, BANNER_BID_REQUEST); + const nativeRequest = spec.buildRequests(nativeBidRequests, NATIVE_BID_REQUEST); const videoRequest = spec.buildRequests(videoBidRequests, VIDEO_BID_REQUEST); it('sends bid request to ENDPOINT via POST', () => { expect(videoRequest.method).to.equal('POST'); expect(bannerRequest.method).to.equal('POST'); + expect(nativeRequest.method).to.equal('POST'); }); it('sends bid request to correct ENDPOINT', () => { expect(videoRequest.url).to.equal(ENDPOINT); expect(bannerRequest.url).to.equal(ENDPOINT); + expect(nativeRequest.url).to.equal(ENDPOINT); }); it('sends correct video bid parameters', () => { @@ -170,6 +285,10 @@ describe('buzzoolaBidAdapter', () => { it('sends correct banner bid parameters', () => { expect(bannerRequest.data).to.deep.equal(BANNER_BID_REQUEST); }); + + it('sends correct native bid parameters', () => { + expect(nativeRequest.data).to.deep.equal(NATIVE_BID_REQUEST); + }); }); describe('interpretResponse', () => { @@ -201,6 +320,10 @@ describe('buzzoolaBidAdapter', () => { nobidServerResponseCheck(BANNER_BID_REQUEST); }); + it('handles native nobid responses', () => { + nobidServerResponseCheck(NATIVE_BID_REQUEST); + }); + it('handles video empty responses', () => { nobidServerResponseCheck(VIDEO_BID_REQUEST, emptyResponse); }); @@ -209,6 +332,10 @@ describe('buzzoolaBidAdapter', () => { nobidServerResponseCheck(BANNER_BID_REQUEST, emptyResponse); }); + it('handles native empty responses', () => { + nobidServerResponseCheck(NATIVE_BID_REQUEST, emptyResponse); + }); + it('should get correct video bid response', () => { bidServerResponseCheck(VIDEO_RESPONSE, VIDEO_BID_REQUEST, REQUIRED_VIDEO_FIELDS); }); @@ -216,6 +343,10 @@ describe('buzzoolaBidAdapter', () => { it('should get correct banner bid response', () => { bidServerResponseCheck(BANNER_RESPONSE, BANNER_BID_REQUEST, REQUIRED_BANNER_FIELDS); }); + + it('should get correct native bid response', () => { + bidServerResponseCheck(NATIVE_RESPONSE, NATIVE_BID_REQUEST, REQUIRED_NATIVE_FIELDS); + }); }); describe('outstream renderer', () => { @@ -268,6 +399,7 @@ describe('buzzoolaBidAdapter', () => { }; const spy = sinon.spy(window.Buzzoola.Core, 'install'); executeRenderer(renderer, result); + renderer.callback(); expect(spy.called).to.be.true; const spyCall = spy.getCall(0); diff --git a/test/spec/modules/byDataAnalyticsAdapter_spec.js b/test/spec/modules/byDataAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..90b4e1d53a6 --- /dev/null +++ b/test/spec/modules/byDataAnalyticsAdapter_spec.js @@ -0,0 +1,139 @@ +import ascAdapter from 'modules/byDataAnalyticsAdapter'; +import { expect } from 'chai'; +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); +let constants = require('src/constants.json'); +let auctionId = 'b70ef967-5c5b-4602-831e-f2cf16e59af2'; +const initOptions = { + clientId: 'asc00000', + logFrequency: 1, +}; +let userData = { + userId: '5da77-ec87-277b-8e7a5', + client_id: 'asc00000', + plateform_name: 'Macintosh', + os_version: 10.157, + browser_name: 'Chrome', + browser_version: 92.04515107, + screen_size: { + width: 1440, + height: 900 + }, + device_type: 'Desktop', + time_zone: 'Asia/Calcutta' +}; +let bidTimeoutArgs = [{ + auctionId, + bidId: '12e90cb5ddc5dea', + bidder: 'appnexus', + adUnitCode: 'div-gpt-ad-mrec1' +}]; +let noBidArgs = { + adUnitCode: 'div-gpt-ad-mrec1', + auctionId, + bidId: '14480e9832f2d2b', + bidder: 'appnexus', + bidderRequestId: '13b87b6c20d3636', + mediaTypes: {banner: {sizes: [[300, 250], [250, 250]]}}, + sizes: [[300, 250], [250, 250]], + src: 'client', + transactionId: 'c8ee3914-1ee0-4ce6-9126-748d5692188c' +} +let auctionEndArgs = { + adUnitCodes: ['div-gpt-ad-mrec1'], + adUnits: [{ + code: 'div-gpt-ad-mrec1', + mediaTypes: {banner: {sizes: [[300, 250], [250, 250]]}}, + sizes: [[300, 250], [250, 250]], + bids: [{bidder: 'appnexus', params: {placementId: '19305195'}}], + transactionId: 'c8ee3914-1ee0-4ce6-9126-748d5692188c' + }], + auctionEnd: 1627973487504, + auctionId, + auctionStatus: 'completed', + timestamp: 1627973484504, + bidsReceived: [], + bidderRequests: [{ + auctionId, + auctionStart: 1627973484504, + bidderCode: 'appnexus', + bidderRequestId: '13b87b6c20d3636', + bids: [ + { + adUnitCode: 'div-gpt-ad-mrec1', + auctionId, + bidId: '14480e9832f2d2b', + bidder: 'appnexus', + bidderRequestId: '13b87b6c20d3636', + src: 'client', + mediaTypes: {banner: {sizes: [[300, 250], [250, 250]]}}, + sizes: [[300, 250], [250, 250]], + transactionId: 'c8ee3914-1ee0-4ce6-9126-748d5692188c' + } + ] + }] +} +let expectedDataArgs = { + visitor_data: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZGE3Ny1lYzg3LTI3N2ItOGU3YTUiLCJjbGllbnRfaWQiOiJhc2MwMDAwMCIsInBsYXRlZm9ybV9uYW1lIjoiTWFjaW50b3NoIiwib3NfdmVyc2lvbiI6MTAuMTU3LCJicm93c2VyX25hbWUiOiJDaHJvbWUiLCJicm93c2VyX3ZlcnNpb24iOjkyLjA0NTE1MTA3LCJzY3JlZW5fc2l6ZSI6eyJ3aWR0aCI6MTQ0MCwiaGVpZ2h0Ijo5MDB9LCJkZXZpY2VfdHlwZSI6IkRlc2t0b3AiLCJ0aW1lX3pvbmUiOiJBc2lhL0NhbGN1dHRhIn0.jNKjsb3Q-ZjkVMcbss_dQFOmu_GdkGqd7t9MbRmqlG4YEMorcJHhUVmUuPi-9pKvC9_t4XlgjED90UieCvdxCQ', + auction_id: auctionId, + auction_start: 1627973484504, + auctionData: [ { + 'adUnit': 'div-gpt-ad-mrec1', + 'size': '300x250', + 'media_type': 'display', + 'bids_bidder': 'appnexus', + 'bids_bid_id': '14480e9832f2d2b' + }, { + 'adUnit': 'div-gpt-ad-mrec1', + 'size': '250x250', + 'media_type': 'display', + 'bids_bidder': 'appnexus', + 'bids_bid_id': '14480e9832f2d2b' + }] +} + +describe('byData Analytics Adapter ', () => { + beforeEach(() => { + sinon.stub(events, 'getEvents').returns([]); + }); + afterEach(() => { + events.getEvents.restore(); + }); + + describe('enableAnalytics ', function () { + beforeEach(() => { + sinon.spy(ascAdapter, 'track'); + }); + afterEach(() => { + ascAdapter.disableAnalytics(); + ascAdapter.track.restore(); + }); + it('should init with correct options', function () { + ascAdapter.enableAnalytics(initOptions) + // Step 1: Initialize adapter + adapterManager.enableAnalytics({ + provider: 'bydata', + options: initOptions + }); + expect(ascAdapter.initOptions).to.have.property('clientId', 'asc00000'); + expect(ascAdapter.initOptions).to.have.property('logFrequency', 1); + }); + }); + + describe('track-events', function () { + ascAdapter.enableAnalytics(initOptions) + // Step 1: Initialize adapter + adapterManager.enableAnalytics({ + provider: 'bydata', + options: initOptions + }); + it('sends and formatted auction data ', function () { + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgs); + events.emit(constants.EVENTS.NO_BID, noBidArgs); + var userToken = ascAdapter.getVisitorData(userData); + var newAuData = ascAdapter.dataProcess(auctionEndArgs); + newAuData['visitor_data'] = userToken; + expect(newAuData).to.deep.equal(expectedDataArgs); + }); + }); +}); diff --git a/test/spec/modules/byplayBidAdapter_spec.js b/test/spec/modules/byplayBidAdapter_spec.js deleted file mode 100644 index 57aad403c4e..00000000000 --- a/test/spec/modules/byplayBidAdapter_spec.js +++ /dev/null @@ -1,93 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/byplayBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import * as bidderFactory from 'src/adapters/bidderFactory.js'; - -describe('byplayBidAdapter', () => { - describe('isBidRequestValid', () => { - describe('exist sectionId', () => { - const bid = { - 'bidder': 'byplay', - 'params': { - 'sectionId': '11111' - }, - }; - - it('should equal true', () => { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - }); - - describe('not exist sectionId', () => { - const bid = { - 'bidder': 'byplay', - 'params': { - }, - }; - - it('should equal false', () => { - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - }); - - describe('buildRequests', () => { - const bids = [ - { - 'bidder': 'byplay', - 'bidId': '1234', - 'params': { - 'sectionId': '1111' - }, - } - ]; - - const request = spec.buildRequests(bids); - - it('should return POST', () => { - expect(request[0].method).to.equal('POST'); - }); - - it('should return data', () => { - expect(request[0].data).to.equal('{"requestId":"1234","sectionId":"1111"}'); - }); - }); - - describe('interpretResponse', () => { - const serverResponse = { - body: { - 'cpm': 1500, - 'width': 320, - 'height': 180, - 'netRevenue': true, - 'creativeId': '1', - 'requestId': '209c1fb5ad88f5', - 'vastXml': '' - } - }; - - const bidderRequest = { - 'method': 'GET', - 'url': 'https://tasp0g98f2.execute-api.ap-northeast-1.amazonaws.com/v1/bidder', - 'data': '{"requestId":"209c1fb5ad88f5","sectionId":7986}' - }; - - const result = spec.interpretResponse(serverResponse, bidderRequest); - - it('should return Array', () => { - expect(Array.isArray(result)).to.equal(true); - }); - - it('should get the correct bid response', () => { - expect(result[0].cpm).to.equal(1500); - expect(result[0].creativeId).to.equal('1'); - expect(result[0].width).to.equal(320); - expect(result[0].height).to.equal(180); - expect(result[0].mediaType).to.equal('video'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].requestId).to.equal('209c1fb5ad88f5'); - expect(result[0].ttl).to.equal(3000); - expect(result[0].vastXml).to.equal(''); - }); - }); -}); diff --git a/test/spec/modules/c1xBidAdapter_spec.js b/test/spec/modules/c1xBidAdapter_spec.js deleted file mode 100644 index 00741abda7a..00000000000 --- a/test/spec/modules/c1xBidAdapter_spec.js +++ /dev/null @@ -1,182 +0,0 @@ -import { expect } from 'chai'; -import { c1xAdapter } from 'modules/c1xBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -const ENDPOINT = 'https://ht.c1exchange.com/ht'; -const BIDDER_CODE = 'c1x'; - -describe('C1XAdapter', function () { - const adapter = newBidder(c1xAdapter); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': BIDDER_CODE, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'params': { - 'siteId': '9999' - } - }; - - it('should return true when required params are passed', function () { - expect(c1xAdapter.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'siteId': null - }; - expect(c1xAdapter.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let bidRequests = [ - { - 'bidder': BIDDER_CODE, - 'params': { - 'siteId': '9999' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - const parseRequest = (data) => { - const parsedData = '{"' + data.replace(/=|&/g, (foundChar) => { - if (foundChar == '=') return '":"'; - else if (foundChar == '&') return '","'; - }) + '"}' - return parsedData; - }; - - it('sends bid request to ENDPOINT via GET', function () { - const request = c1xAdapter.buildRequests(bidRequests); - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('GET'); - }); - - it('should generate correct bid Id tag', function () { - const request = c1xAdapter.buildRequests(bidRequests); - expect(request.bids[0].adUnitCode).to.equal('adunit-code'); - expect(request.bids[0].bidId).to.equal('30b31c1838de1e'); - }); - - it('should convert params to proper form and attach to request', function () { - const request = c1xAdapter.buildRequests(bidRequests); - const originalPayload = parseRequest(request.data); - const payloadObj = JSON.parse(originalPayload); - expect(payloadObj.adunits).to.equal('1'); - expect(payloadObj.a1s).to.equal('300x250,300x600'); - expect(payloadObj.a1).to.equal('adunit-code'); - expect(payloadObj.site).to.equal('9999'); - }); - - it('should convert floor price to proper form and attach to request', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - 'params': { - 'siteId': '9999', - 'floorPriceMap': { - '300x250': 4.35 - } - } - }); - const request = c1xAdapter.buildRequests([bidRequest]); - const originalPayload = parseRequest(request.data); - const payloadObj = JSON.parse(originalPayload); - expect(payloadObj.a1p).to.equal('4.35'); - }); - - it('should convert pageurl to proper form and attach to request', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - 'params': { - 'siteId': '9999', - 'pageurl': 'https://c1exchange.com/' - } - }); - const request = c1xAdapter.buildRequests([bidRequest]); - const originalPayload = parseRequest(request.data); - const payloadObj = JSON.parse(originalPayload); - expect(payloadObj.pageurl).to.equal('https://c1exchange.com/'); - }); - - it('should convert GDPR Consent to proper form and attach to request', function () { - let consentString = 'BOP2gFWOQIFovABABAENBGAAAAAAMw'; - let bidderRequest = { - 'bidderCode': 'c1x', - 'gdprConsent': { - 'consentString': consentString, - 'gdprApplies': true - } - } - bidderRequest.bids = bidRequests; - - const request = c1xAdapter.buildRequests(bidRequests, bidderRequest); - const originalPayload = parseRequest(request.data); - const payloadObj = JSON.parse(originalPayload); - expect(payloadObj['consent_string']).to.equal('BOP2gFWOQIFovABABAENBGAAAAAAMw'); - expect(payloadObj['consent_required']).to.equal('true'); - }); - }); - - describe('interpretResponse', function () { - let response = { - 'bid': true, - 'cpm': 1.5, - 'ad': '', - 'width': 300, - 'height': 250, - 'crid': '8888', - 'adId': 'c1x-test', - 'bidType': 'GROSS_BID' - }; - - it('should get correct bid response', function () { - let expectedResponse = [ - { - width: 300, - height: 250, - cpm: 1.5, - ad: '', - creativeId: '8888', - currency: 'USD', - ttl: 300, - netRevenue: false, - requestId: 'yyyy' - } - ]; - let bidderRequest = {}; - bidderRequest.bids = [ - { adUnitCode: 'c1x-test', - bidId: 'yyyy' } - ]; - let result = c1xAdapter.interpretResponse({ body: [response] }, bidderRequest); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); - }); - - it('handles nobid responses', function () { - let response = { - bid: false, - adId: 'c1x-test' - }; - let bidderRequest = {}; - let result = c1xAdapter.interpretResponse({ body: [response] }, bidderRequest); - expect(result.length).to.equal(0); - }); - }); -}); diff --git a/test/spec/modules/ccxBidAdapter_spec.js b/test/spec/modules/ccxBidAdapter_spec.js index ef86b391e39..d346a14d38a 100644 --- a/test/spec/modules/ccxBidAdapter_spec.js +++ b/test/spec/modules/ccxBidAdapter_spec.js @@ -434,4 +434,58 @@ describe('ccxAdapter', function () { expect(spec.getUserSyncs(syncOptions, [{body: response}])).to.be.empty; }); }); + describe('mediaTypesVideoParams', function () { + it('Valid video mediaTypes', function () { + let bids = [ + { + adUnitCode: 'video', + auctionId: '0b9de793-8eda-481e-a548-c187d58b28d9', + bidId: '3u94t90ut39tt3t', + bidder: 'ccx', + bidderRequestId: '23ur20r239r2r', + mediaTypes: { + video: { + playerSize: [[640, 480]], + protocols: [2, 3, 5, 6], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [1, 2, 3, 4], + skip: 1, + skipafter: 5 + } + }, + params: { + placementId: 608 + }, + sizes: [[640, 480]], + transactionId: 'aefddd38-cfa0-48ab-8bdd-325de4bab5f9' + } + ]; + + let imps = [ + { + video: { + w: 640, + h: 480, + protocols: [2, 3, 5, 6], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [1, 2, 3, 4], + skip: 1, + skipafter: 5 + }, + id: '3u94t90ut39tt3t', + secure: 1, + ext: { + pid: 608 + } + } + ]; + + let bidsClone = utils.deepClone(bids); + + let response = spec.buildRequests(bidsClone, {'bids': bidsClone}); + let data = JSON.parse(response.data); + + expect(data.imp).to.deep.have.same.members(imps); + }); + }); }); diff --git a/test/spec/modules/cedatoBidAdapter_spec.js b/test/spec/modules/cedatoBidAdapter_spec.js deleted file mode 100644 index a7f4875afff..00000000000 --- a/test/spec/modules/cedatoBidAdapter_spec.js +++ /dev/null @@ -1,133 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/cedatoBidAdapter.js'; - -describe('the cedato adapter', function () { - function getValidBidObject() { - return { - bidId: '2f4a613a702b6c', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - params: { - player_id: 1450133326, - } - }; - }; - - describe('isBidRequestValid', function() { - var bid; - - beforeEach(function() { - bid = getValidBidObject(); - }); - - it('should fail validation if the bid isn\'t defined or not an object', function() { - var result = spec.isBidRequestValid(); - - expect(result).to.equal(false); - - result = spec.isBidRequestValid('not an object'); - - expect(result).to.equal(false); - }); - }); - - describe('buildRequests', function() { - var bid, bidRequestObj; - - beforeEach(function() { - bid = getValidBidObject(); - bidRequestObj = { - refererInfo: {referer: 'prebid.js'}, - gdprConsent: { - consentString: 'test-string', - gdprApplies: true - }, - uspConsent: '1NYN' - }; - }); - - it('should build a very basic request', function() { - var [request] = spec.buildRequests([bid], bidRequestObj); - expect(request.method).to.equal('POST'); - }); - - it('should pass gdpr and usp strings to server', function() { - var [request] = spec.buildRequests([bid], bidRequestObj); - var payload = JSON.parse(request.data); - expect(payload.gdpr_consent).to.not.be.undefined; - expect(payload.gdpr_consent.consent_string).to.equal(bidRequestObj.gdprConsent.consentString); - expect(payload.gdpr_consent.consent_required).to.equal(bidRequestObj.gdprConsent.gdprApplies); - expect(payload.us_privacy).to.equal(bidRequestObj.uspConsent); - }); - }); - - describe('interpretResponse', function() { - var bid, serverResponse, bidderRequest; - - beforeEach(function() { - bid = getValidBidObject(); - serverResponse = { - body: { - bidid: '0.36157306192821', - seatbid: [ - { - seat: '0', - bid: [{ - gp: { - 'negative': 0.496954, - 'positive': 0.503046, - 'class': '0' - }, - id: '0.75549202124378', - adomain: 'cedato.com', - uuid: bid.bidId, - crid: '1450133326', - adm: "
\n\n\n", - h: 250, - w: 300, - price: '0.1' - }] - } - ], - cur: 'USD' - } - }; - bidderRequest = { - bids: [bid] - }; - }); - - it('should return an array of bid responses', function() { - var responses = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(responses).to.be.an('array').with.length(1); - }); - }); - - describe('getUserSyncs', function() { - var bid; - - beforeEach(function() { - bid = getValidBidObject(); - }); - - it('should sync with iframe', function() { - var syncs = spec.getUserSyncs({ iframeEnabled: true }, null, { - consentString: '', - gdprApplies: true - }); - - expect(syncs).to.be.an('array').with.length(1); - expect(syncs[0].type).to.equal('iframe'); - }); - - it('should sync with image', function() { - var syncs = spec.getUserSyncs({ pixelEnabled: true }); - - expect(syncs).to.be.an('array').with.length(1); - expect(syncs[0].type).to.equal('image'); - }); - }); -}); diff --git a/test/spec/modules/cleanioRtdProvider_spec.js b/test/spec/modules/cleanioRtdProvider_spec.js new file mode 100644 index 00000000000..47c4b1b4961 --- /dev/null +++ b/test/spec/modules/cleanioRtdProvider_spec.js @@ -0,0 +1,188 @@ +import * as utils from '../../../src/utils.js'; +import * as hook from '../../../src/hook.js' + +import { __TEST__ } from '../../../modules/cleanioRtdProvider.js'; + +const { + readConfig, + ConfigError, + pageInitStepPreloadScript, + pageInitStepProtectPage, + bidWrapStepAugmentHtml, + bidWrapStepProtectByWrapping, + beforeInit, +} = __TEST__; + +sinon.assert.expose(chai.assert, { prefix: 'sinon' }); + +const fakeScriptURL = 'https://example.com/script.js'; + +function makeFakeBidResponse() { + return { + ad: 'hello ad', + bidderCode: 'BIDDER', + creativeId: 'CREATIVE', + cpm: 1.23, + }; +} + +describe('clean.io RTD module', function () { + describe('readConfig()', function() { + it('should throw ConfigError on invalid configurations', function() { + expect(() => readConfig({})).to.throw(ConfigError); + expect(() => readConfig({ params: {} })).to.throw(ConfigError); + expect(() => readConfig({ params: { protectionMode: 'bids' } })).to.throw(ConfigError); + expect(() => readConfig({ params: { cdnUrl: 'abc' } })).to.throw(ConfigError); + expect(() => readConfig({ params: { cdnUrl: 'abc', protectionMode: 'bids' } })).to.throw(ConfigError); + expect(() => readConfig({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: '123' } })).to.throw(ConfigError); + }); + + it('should accept valid configurations', function() { + expect(() => readConfig({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'full' } })).to.not.throw(); + expect(() => readConfig({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'bids' } })).to.not.throw(); + expect(() => readConfig({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'bids-nowait' } })).to.not.throw(); + }); + }); + + describe('Module initialization step', function() { + let insertElementStub; + beforeEach(function() { + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + afterEach(function() { + utils.insertElement.restore(); + }); + + it('pageInitStepPreloadScript() should insert link/preload element', function() { + pageInitStepPreloadScript(fakeScriptURL); + + sinon.assert.calledOnce(insertElementStub); + sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'LINK')); + sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.rel === 'preload')); + sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.as === 'script')); + sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.href === fakeScriptURL)); + }); + + it('pageInitStepProtectPage() should insert script element', function() { + pageInitStepProtectPage(fakeScriptURL); + + sinon.assert.calledOnce(insertElementStub); + sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'SCRIPT')); + sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.type === 'text/javascript')); + sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.src === fakeScriptURL)); + }); + }); + + function ensurePrependToBidResponse(fakeBidResponse) { + expect(fakeBidResponse).to.have.own.property('ad').which.is.a('string'); + expect(fakeBidResponse.ad).to.contain(''); + } + + function ensureWrapBidResponse(fakeBidResponse, scriptUrl) { + expect(fakeBidResponse).to.have.own.property('ad').which.is.a('string'); + expect(fakeBidResponse.ad).to.contain(`src="${scriptUrl}"`); + expect(fakeBidResponse.ad).to.contain('agent.put(ad)'); + } + + describe('Bid processing step', function() { + it('bidWrapStepAugmentHtml() should prepend bid-specific information in a comment', function() { + const fakeBidResponse = makeFakeBidResponse(); + bidWrapStepAugmentHtml(fakeBidResponse); + ensurePrependToBidResponse(fakeBidResponse); + }); + + it('bidWrapStepProtectByWrapping() should wrap payload into a script tag', function() { + const fakeBidResponse = makeFakeBidResponse(); + bidWrapStepProtectByWrapping(fakeScriptURL, 0, fakeBidResponse); + ensureWrapBidResponse(fakeBidResponse, fakeScriptURL); + }); + }); + + describe('Sumbodule execution', function() { + let submoduleStub; + let insertElementStub; + beforeEach(function () { + submoduleStub = sinon.stub(hook, 'submodule'); + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + afterEach(function () { + utils.insertElement.restore(); + submoduleStub.restore(); + }); + + function getModule() { + beforeInit(); + + expect(submoduleStub.calledOnceWith('realTimeData')).to.equal(true); + + const registeredSubmoduleDefinition = submoduleStub.getCall(0).args[1]; + expect(registeredSubmoduleDefinition).to.be.an('object'); + expect(registeredSubmoduleDefinition).to.have.own.property('name', 'clean.io'); + expect(registeredSubmoduleDefinition).to.have.own.property('init').that.is.a('function'); + expect(registeredSubmoduleDefinition).to.have.own.property('onBidResponseEvent').that.is.a('function'); + + return registeredSubmoduleDefinition; + } + + it('should register clean.io RTD submodule provider', function () { + getModule(); + }); + + it('should refuse initialization with incorrect parameters', function () { + const { init } = getModule(); + expect(init({ params: { cdnUrl: 'abc', protectionMode: 'full' } }, {})).to.equal(false); // too short distribution name + sinon.assert.notCalled(insertElementStub); + }); + + it('should iniitalize in full (page) protection mode', function () { + const { init, onBidResponseEvent } = getModule(); + expect(init({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'full' } }, {})).to.equal(true); + sinon.assert.calledOnce(insertElementStub); + sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'SCRIPT')); + + const fakeBidResponse = makeFakeBidResponse(); + onBidResponseEvent(fakeBidResponse, {}, {}); + ensurePrependToBidResponse(fakeBidResponse); + }); + + it('should iniitalize in bids (frame) protection mode', function () { + const { init, onBidResponseEvent } = getModule(); + expect(init({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'bids' } }, {})).to.equal(true); + sinon.assert.calledOnce(insertElementStub); + sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'LINK')); + + const fakeBidResponse = makeFakeBidResponse(); + onBidResponseEvent(fakeBidResponse, {}, {}); + ensureWrapBidResponse(fakeBidResponse, 'https://abc1234567890.cloudfront.net/script.js'); + }); + + it('should respect preload status in bids-nowait protection mode', function () { + const { init, onBidResponseEvent } = getModule(); + expect(init({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'bids-nowait' } }, {})).to.equal(true); + sinon.assert.calledOnce(insertElementStub); + sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'LINK')); + const preloadLink = insertElementStub.getCall(0).args[0]; + expect(preloadLink).to.have.property('onload').which.is.a('function'); + expect(preloadLink).to.have.property('onerror').which.is.a('function'); + + const fakeBidResponse1 = makeFakeBidResponse(); + onBidResponseEvent(fakeBidResponse1, {}, {}); + ensurePrependToBidResponse(fakeBidResponse1); + + // Simulate successful preloading + preloadLink.onload(); + + const fakeBidResponse2 = makeFakeBidResponse(); + onBidResponseEvent(fakeBidResponse2, {}, {}); + ensureWrapBidResponse(fakeBidResponse2, 'https://abc1234567890.cloudfront.net/script.js'); + + // Simulate error + preloadLink.onerror(); + + // Now we should fallback to just prepending + const fakeBidResponse3 = makeFakeBidResponse(); + onBidResponseEvent(fakeBidResponse3, {}, {}); + ensurePrependToBidResponse(fakeBidResponse3); + }); + }); +}); diff --git a/test/spec/modules/cleanmedianetBidAdapter_spec.js b/test/spec/modules/cleanmedianetBidAdapter_spec.js index 5438f6c8701..c2eea6f32d7 100644 --- a/test/spec/modules/cleanmedianetBidAdapter_spec.js +++ b/test/spec/modules/cleanmedianetBidAdapter_spec.js @@ -31,22 +31,6 @@ describe('CleanmedianetAdapter', function () { ).to.equal(true); }); - it('should validate bid floor', function() { - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) - ).to.equal(true); // bidfloor has a default - expect( - spec.isBidRequestValid({ - params: { supplyPartnerId: '123', bidfloor: '123' } - }) - ).to.equal(false); - expect( - spec.isBidRequestValid({ - params: { supplyPartnerId: '123', bidfloor: 0.1 } - }) - ).to.equal(true); - }); - it('should validate adpos', function() { expect( spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) @@ -159,15 +143,6 @@ describe('CleanmedianetAdapter', function () { expect(response.data.imp[0].instl).to.equal( bidRequestWithInstlEquals0.params.instl ); - const bidRequestWithBidfloorEquals1 = utils.deepClone(bidRequest); - bidRequestWithBidfloorEquals1.params.bidfloor = 1; - response = spec.buildRequests( - [bidRequestWithBidfloorEquals1], - bidRequest2 - )[0]; - expect(response.data.imp[0].bidfloor).to.equal( - bidRequestWithBidfloorEquals1.params.bidfloor - ); }); it('builds request banner object correctly', function() { diff --git a/test/spec/modules/clickforceBidAdapter_spec.js b/test/spec/modules/clickforceBidAdapter_spec.js index dad00f94641..c4c4d77e954 100644 --- a/test/spec/modules/clickforceBidAdapter_spec.js +++ b/test/spec/modules/clickforceBidAdapter_spec.js @@ -75,7 +75,10 @@ describe('ClickforceAdapter', function () { 'currency': 'USD', 'ttl': 60, 'netRevenue': true, - 'zone': '6682' + 'zone': '6682', + 'adomain': [ + 'www.example.com' + ] }]; let response1 = [{ @@ -116,6 +119,11 @@ describe('ClickforceAdapter', function () { 'ttl': 60, 'ad': '', 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': [ + 'www.example.com' + ] + } }]; let expectedResponse1 = [{ @@ -144,6 +152,9 @@ describe('ClickforceAdapter', function () { }, 'clickUrl': 'cu', 'impressionTrackers': ['iu'] + }, + 'meta': { + 'advertiserDomains': [] } }]; diff --git a/test/spec/modules/clicktripzBidAdapter_spec.js b/test/spec/modules/clicktripzBidAdapter_spec.js deleted file mode 100644 index fed94811c4e..00000000000 --- a/test/spec/modules/clicktripzBidAdapter_spec.js +++ /dev/null @@ -1,152 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/clicktripzBidAdapter.js'; - -const ENDPOINT_URL = 'https://www.clicktripz.com/x/prebid/v1'; - -describe('clicktripzBidAdapter', function () { - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'clicktripz', - 'params': { - placementId: 'testPlacementId', - siteId: 'testSiteId' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250] - ], - 'bidId': '1234asdf1234', - 'bidderRequestId': '1234asdf1234asdf', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' - }; - - let bid2 = { - 'bidder': 'clicktripz', - 'params': { - placementId: 'testPlacementId' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250] - ], - 'bidId': '1234asdf1234', - 'bidderRequestId': '1234asdf1234asdf', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' - }; - - let bid3 = { - 'bidder': 'clicktripz', - 'params': { - siteId: 'testSiteId' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250] - ], - 'bidId': '1234asdf1234', - 'bidderRequestId': '1234asdf1234asdf', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are NOT found', function () { - expect(spec.isBidRequestValid(bid2)).to.equal(false); - expect(spec.isBidRequestValid(bid3)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let validBidRequests = [{ - 'bidder': 'clicktripz', - 'params': { - placementId: 'testPlacementId', - siteId: 'testSiteId' - }, - 'sizes': [ - [300, 250], - [300, 300] - ], - 'bidId': '23beaa6af6cdde', - 'bidderRequestId': '19c0c1efdf37e7', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - }, { - 'bidder': 'clicktripz', - 'params': { - placementId: 'testPlacementId2', - siteId: 'testSiteId2' - }, - 'sizes': [ - [300, 250] - ], - 'bidId': '25beaa6af6cdde', - 'bidderRequestId': '19c0c1efdf37e7', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - }]; - - const request = spec.buildRequests(validBidRequests); - it('sends bid request to our endpoint via POST', function () { - expect(request.method).to.equal('POST'); - }); - it('sends bid request to our endpoint via POST', function () { - expect(request.method).to.equal('POST'); - }); - - it('sends bid request to our endpoint at the correct URL', function () { - expect(request.url).to.equal(ENDPOINT_URL); - }); - it('sends bid request to our endpoint at the correct URL', function () { - expect(request.url).to.equal(ENDPOINT_URL); - }); - - it('transforms sizes into an array of strings. Pairs of concatenated sizes joined with an x', function () { - expect(request.data[0].sizes.toString()).to.equal('300x250,300x300'); - }); - it('transforms sizes into an array of strings. Pairs of concatenated sizes joined with an x', function () { - expect(request.data[1].sizes.toString()).to.equal('300x250'); - }); - - it('includes bidId, siteId, and placementId in payload', function () { - expect(request.data[0].bidId).to.equal('23beaa6af6cdde'); - expect(request.data[0].siteId).to.equal('testSiteId'); - expect(request.data[0].placementId).to.equal('testPlacementId'); - }); - it('includes bidId, siteId, and placementId in payload', function () { - expect(request.data[1].bidId).to.equal('25beaa6af6cdde'); - expect(request.data[1].siteId).to.equal('testSiteId2'); - expect(request.data[1].placementId).to.equal('testPlacementId2'); - }); - }); - - describe('interpretResponse', function () { - let serverResponse = { - body: [{ - 'bidId': 'bid-request-id', - 'ttl': 120, - 'netRevenue': true, - 'size': '300x200', - 'currency': 'USD', - 'adUrl': 'https://www.clicktripz.com/n3/crane/v0/render?t=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXlsb2FkIjoiaHR0cHM6XC9cL3d3dy5jbGlja3RyaXB6LmNvbVwvY2xpY2sucGhwP2NhbXBhaWduSUQ9MTkxNTYmcHJlQ2hlY2tlZD0xJnB1Ymxpc2hlcklEPTM2MCZzZWFyY2hLZXk9N2M5MzQ0NzhlM2M1NTc3Y2EyN2ZmN2Y1NTg5N2NkMzkmc2VhcmNoRGlzcGxheVR5cGU9MSZkaXNwbGF5VHlwZT00JmNyZWF0aXZlVHlwZT1zaW5nbGUmaXNQb3BVbmRlcj0wJnBvc2l0aW9uPTEmdHlwZT0xJmNpdHk9TWFkcmlkJTJDK1NwYWluJmNoZWNrSW5EYXRlPTAzJTJGMDElMkYyMDIwJmNoZWNrT3V0RGF0ZT0wMyUyRjA1JTJGMjAyMCZndWVzdHM9MiZyb29tcz0xIn0.WBDGYr1qfkSvOuK02VpMW3iAua1E02jjDGDViFc2kaE', - 'creativeId': '25ef9876abc5681f153', - 'cpm': 50 - }] - }; - it('should get the correct bid response', function () { - let expectedResponse = [{ - 'requestId': 'bid-request-id', - 'cpm': 50, - 'netRevenue': true, - 'width': '300', - 'height': '200', - 'creativeId': '25ef9876abc5681f153', - 'currency': 'USD', - 'adUrl': 'https://www.clicktripz.com/n3/crane/v0/render?t=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXlsb2FkIjoiaHR0cHM6XC9cL3d3dy5jbGlja3RyaXB6LmNvbVwvY2xpY2sucGhwP2NhbXBhaWduSUQ9MTkxNTYmcHJlQ2hlY2tlZD0xJnB1Ymxpc2hlcklEPTM2MCZzZWFyY2hLZXk9N2M5MzQ0NzhlM2M1NTc3Y2EyN2ZmN2Y1NTg5N2NkMzkmc2VhcmNoRGlzcGxheVR5cGU9MSZkaXNwbGF5VHlwZT00JmNyZWF0aXZlVHlwZT1zaW5nbGUmaXNQb3BVbmRlcj0wJnBvc2l0aW9uPTEmdHlwZT0xJmNpdHk9TWFkcmlkJTJDK1NwYWluJmNoZWNrSW5EYXRlPTAzJTJGMDElMkYyMDIwJmNoZWNrT3V0RGF0ZT0wMyUyRjA1JTJGMjAyMCZndWVzdHM9MiZyb29tcz0xIn0.WBDGYr1qfkSvOuK02VpMW3iAua1E02jjDGDViFc2kaE', - 'ttl': 120 - }]; - let result = spec.interpretResponse(serverResponse); - expect(result).to.deep.equal(expectedResponse); - }); - }); -}); diff --git a/test/spec/modules/codefuelBidAdapter_spec.js b/test/spec/modules/codefuelBidAdapter_spec.js new file mode 100644 index 00000000000..a2549012d84 --- /dev/null +++ b/test/spec/modules/codefuelBidAdapter_spec.js @@ -0,0 +1,316 @@ +import {expect} from 'chai'; +import {spec} from 'modules/codefuelBidAdapter.js'; +import {config} from 'src/config.js'; +import {server} from 'test/mocks/xhr'; + +const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/92.0.4515.159 Safari/537.36'; +const DEFAULT_USER_AGENT = window.navigator.userAgent; +const setUADefault = () => { window.navigator.__defineGetter__('userAgent', function () { return DEFAULT_USER_AGENT }) }; +const setUAMock = () => { window.navigator.__defineGetter__('userAgent', function () { return USER_AGENT }) }; + +describe('Codefuel Adapter', function () { + describe('Bid request and response', function () { + const commonBidRequest = { + bidder: 'codefuel', + params: { + publisher: { + id: 'publisher-id' + }, + }, + bidId: '2d6815a92ba1ba', + auctionId: '12043683-3254-4f74-8934-f941b085579e', + } + const nativeBidRequestParams = { + nativeParams: { + image: { + required: true, + sizes: [ + 120, + 100 + ], + sendId: true + }, + title: { + required: true, + sendId: true + }, + sponsoredBy: { + required: false + } + }, + } + + const displayBidRequestParams = { + sizes: [ + [ + 300, + 250 + ] + ] + } + + describe('isBidRequestValid', function () { + before(() => { + config.setConfig({ + codefuel: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should fail when bid is invalid', function () { + const bid = { + bidder: 'codefuel', + params: { + publisher: { + id: 'publisher-id', + } + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should not succeed when bid contains native params', function () { + const bid = { + bidder: 'codefuel', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should not succeed when bid contains only sizes', function () { + const bid = { + bidder: 'codefuel', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...displayBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should fail if publisher id is not set', function () { + const bid = { + bidder: 'codefuel', + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should fail if bidder url is not set', function () { + const bid = { + bidder: 'codefuel', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + config.resetConfig() + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + before(() => { + setUAMock() + config.setConfig({ + codefuel: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + setUADefault() + }) + + const commonBidderRequest = { + timeout: 500, + auctionId: '12043683-3254-4f74-8934-f941b085579e', + refererInfo: { + referer: 'https://example.com/', + } + } + + it('should build display request', function () { + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + const expectedData = { + cur: [ + 'USD' + ], + device: { + devicetype: 2, + ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/92.0.4515.159 Safari/537.36' + }, + id: '12043683-3254-4f74-8934-f941b085579e', + imp: [ + { + banner: { + format: [ + { + h: 250, + w: 300 + } + ] + }, + id: '1' + } + ], + site: { + domain: 'example.com', + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + source: { + fd: 1 + }, + tmax: 500 + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid') + expect(res.data).to.deep.equal(expectedData) + }) + + it('should pass bidder timeout', function () { + const bidRequest = { + ...commonBidRequest, + } + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = res.data + expect(resData.tmax).to.equal(500) + }); + }) + + describe('interpretResponse', function () { + it('should return empty array if no valid bids', function () { + const res = spec.interpretResponse({}, []) + expect(res).to.be.an('array').that.is.empty + }); + + it('should interpret display response', function () { + const serverResponse = { + body: { + id: '6b2eedc8-8ff5-46ef-adcf-e701b508943e', + seatbid: [ + { + bid: [ + { + id: 'd90fe7fa-28d7-11eb-8ce4-462a842a7cf9', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '
ad
', + adomain: [ + 'example.com' + ], + cid: '3865084', + crid: '29998660', + cat: [ + 'IAB10-2' + ], + w: 300, + h: 250 + } + ], + seat: 'acc-6536' + } + ], + bidid: 'd90fe7fa-28d7-11eb-8ce4-13d94bfa26f9', + cur: 'USD' + } + } + const request = { + bids: [ + { + ...commonBidRequest, + ...displayBidRequestParams + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '29998660', + ttl: 360, + netRevenue: true, + currency: 'USD', + mediaType: 'banner', + ad: '
ad
', + width: 300, + height: 250, + meta: {'advertiserDomains': []} + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + }) + }) + + describe('getUserSyncs', function () { + const usersyncUrl = 'https://usersync-url.com'; + beforeEach(() => { + config.setConfig({ + codefuel: { + usersyncUrl: usersyncUrl, + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should not return user sync if pixel enabled with codefuel config', function () { + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should not return user sync if pixel disabled', function () { + const ret = spec.getUserSyncs({pixelEnabled: false}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should not return user sync if url is not set', function () { + config.resetConfig() + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should not pass GDPR consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.to.be.an('array').that.is.empty + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.be.an('array').that.is.empty + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.be.an('array').that.is.empty + }); + + it('should not pass US consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.be.an('array').that.is.empty + }); + + it('should pass GDPR and US consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.be.an('array').that.is.empty + }); + }) +}) diff --git a/test/spec/modules/cointrafficBidAdapter_spec.js b/test/spec/modules/cointrafficBidAdapter_spec.js index a2ce4cedea0..3755ddc4c4a 100644 --- a/test/spec/modules/cointrafficBidAdapter_spec.js +++ b/test/spec/modules/cointrafficBidAdapter_spec.js @@ -142,7 +142,9 @@ describe('cointrafficBidAdapter', function () { height: 250, creativeId: 'creativeId12345', ttl: 90, - ad: '

I am an ad

' + ad: '

I am an ad

', + mediaType: 'banner', + adomain: ['test.com'] } }; @@ -155,7 +157,13 @@ describe('cointrafficBidAdapter', function () { height: 250, creativeId: 'creativeId12345', ttl: 90, - ad: '

I am an ad

' + ad: '

I am an ad

', + meta: { + mediaType: 'banner', + advertiserDomains: [ + 'test.com', + ] + } }]; let result = spec.interpretResponse(serverResponse, bidRequest[0]); @@ -186,7 +194,9 @@ describe('cointrafficBidAdapter', function () { height: 250, creativeId: 'creativeId12345', ttl: 90, - ad: '

I am an ad

' + ad: '

I am an ad

', + mediaType: 'banner', + adomain: ['test.com'] } }; @@ -199,7 +209,13 @@ describe('cointrafficBidAdapter', function () { height: 250, creativeId: 'creativeId12345', ttl: 90, - ad: '

I am an ad

' + ad: '

I am an ad

', + meta: { + mediaType: 'banner', + advertiserDomains: [ + 'test.com', + ] + } }]; const getConfigStub = sinon.stub(config, 'getConfig').returns('USD'); diff --git a/test/spec/modules/coinzillaBidAdapter_spec.js b/test/spec/modules/coinzillaBidAdapter_spec.js index a3438b80126..01f22722abf 100644 --- a/test/spec/modules/coinzillaBidAdapter_spec.js +++ b/test/spec/modules/coinzillaBidAdapter_spec.js @@ -85,7 +85,6 @@ describe('coinzillaBidAdapter', function () { 'bidId': 'bidId123', 'referer': 'www.example.com' } - } ]; let serverResponse = { @@ -98,7 +97,9 @@ describe('coinzillaBidAdapter', function () { 'requestId': 'bidId123', 'width': 300, 'height': 250, - 'netRevenue': true + 'netRevenue': true, + 'mediaType': 'banner', + 'advertiserDomain': ['none.com'] } }; it('should get the correct bid response', function () { @@ -111,7 +112,9 @@ describe('coinzillaBidAdapter', function () { 'currency': 'EUR', 'netRevenue': true, 'ttl': 3000, - 'ad': '

I am an ad

' + 'ad': '

I am an ad

', + 'mediaType': 'banner', + 'meta': {'advertiserDomains': ['none.com']} }]; let result = spec.interpretResponse(serverResponse, bidRequest[0]); expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); diff --git a/test/spec/modules/collectcentBidAdapter_spec.js b/test/spec/modules/collectcentBidAdapter_spec.js deleted file mode 100644 index 0ab83a8024b..00000000000 --- a/test/spec/modules/collectcentBidAdapter_spec.js +++ /dev/null @@ -1,118 +0,0 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/collectcentBidAdapter.js'; - -describe('Collectcent', function () { - let bid = { - bidId: '2dd581a2b6281d', - bidder: 'collectcent', - bidderRequestId: '145e1d6a7837c9', - params: { - placementId: 123, - traffic: 'banner' - }, - placementCode: 'placement_0', - auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - sizes: [[300, 250]], - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' - }; - - describe('isBidRequestValid', function () { - it('Should return true when placementId can be cast to a number', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; - }); - it('Should return false when placementId is not a number', function () { - bid.params.placementId = 'aaa'; - expect(spec.isBidRequestValid(bid)).to.be.false; - }); - }); - - describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid]); - it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequest).to.exist; - expect(serverRequest.method).to.exist; - expect(serverRequest.url).to.exist; - expect(serverRequest.data).to.exist; - }); - it('Returns POST method', function () { - expect(serverRequest.method).to.equal('POST'); - }); - it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://publishers.motionspots.com/?c=o&m=multi'); - }); - it('Returns valid data if array of bids is valid', function () { - let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'secure', 'host', 'page', 'placements'); - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.secure).to.be.within(0, 1); - expect(data.host).to.be.a('string'); - expect(data.page).to.be.a('string'); - let placements = data['placements']; - for (let i = 0; i < placements.length; i++) { - let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'bidId', 'traffic', 'sizes'); - expect(placement.placementId).to.be.a('number'); - expect(placement.bidId).to.be.a('string'); - expect(placement.traffic).to.be.a('string'); - expect(placement.sizes).to.be.an('array'); - } - }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); - }); - describe('interpretResponse', function () { - let resObject = { - body: [ { - requestId: '123', - mediaType: 'banner', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD' - } ] - }; - let serverResponses = spec.interpretResponse(resObject); - it('Returns an array of valid server responses if response object is valid', function () { - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - let dataItem = serverResponses[i]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - expect(dataItem.ad).to.be.a('string'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.mediaType).to.be.a('string'); - } - it('Returns an empty array if invalid response is passed', function () { - serverResponses = spec.interpretResponse('invalid_response'); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - }); - }); - - describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs(); - it('Returns valid URL and `', function () { - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.exist; - expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://publishers.motionspots.com/?c=o&m=cookie'); - }); - }); -}); diff --git a/test/spec/modules/colombiaBidAdapter_spec.js b/test/spec/modules/colombiaBidAdapter_spec.js deleted file mode 100644 index 4e80c6b1d9d..00000000000 --- a/test/spec/modules/colombiaBidAdapter_spec.js +++ /dev/null @@ -1,152 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/colombiaBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; - -const HOST_NAME = document.location.protocol + '//' + window.location.host; -const ENDPOINT = 'https://ade.clmbtech.com/cde/prebid.htm'; - -describe('colombiaBidAdapter', function() { - const adapter = newBidder(spec); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'colombia', - 'params': { - placementId: '307466' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250] - ], - 'bidId': '23beaa6af6cdde', - 'bidderRequestId': '19c0c1efdf37e7', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when placementId not passed correctly', function () { - bid.params.placementId = ''; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let bidRequests = [ - { - 'bidder': 'colombia', - 'params': { - placementId: '307466' - }, - 'adUnitCode': 'adunit-code1', - 'sizes': [ - [300, 250] - ], - 'bidId': '23beaa6af6cdde', - 'bidderRequestId': '19c0c1efdf37e7', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - }, - { - 'bidder': 'colombia', - 'params': { - placementId: '307466' - }, - 'adUnitCode': 'adunit-code2', - 'sizes': [ - [300, 250] - ], - 'bidId': '382091349b149f"', - 'bidderRequestId': '"1f9c98192de251"', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - } - ]; - - const request = spec.buildRequests(bidRequests); - - it('sends bid request to our endpoint via POST', function () { - expect(request[0].method).to.equal('POST'); - expect(request[1].method).to.equal('POST'); - }); - - it('attaches source and version to endpoint URL as query params', function () { - expect(request[0].url).to.equal(ENDPOINT); - expect(request[1].url).to.equal(ENDPOINT); - }); - }); - - describe('interpretResponse', function () { - let bidRequest = [ - { - 'method': 'POST', - 'url': 'https://ade.clmbtech.com/cde/prebid.htm', - 'data': { - 'v': 'hb1', - 'p': '307466', - 'w': '300', - 'h': '250', - 'cb': 12892917383, - 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', - 'uid': '23beaa6af6cdde', - 't': 'i', - } - } - ]; - - let serverResponse = { - body: { - 'ad': '
This is test case for colombia adapter
', - 'cpm': 3.14, - 'creativeId': '6b958110-612c-4b03-b6a9-7436c9f746dc-1sk24', - 'currency': 'USD', - 'uid': '23beaa6af6cdde', - 'width': 728, - 'height': 90, - 'netRevenue': true, - 'ttl': 600, - 'dealid': '', - 'referrer': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836' - } - }; - - it('should get the correct bid response', function () { - let expectedResponse = [{ - 'requestId': '23beaa6af6cdde', - 'cpm': 3.14, - 'width': 728, - 'height': 90, - 'creativeId': '6b958110-612c-4b03-b6a9-7436c9f746dc-1sk24', - 'dealId': '', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 300, - 'referrer': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', - 'ad': '
This is test case for colombia adapter
' - }]; - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); - }); - - it('handles empty bid response', function () { - let response = { - body: { - 'uid': '23beaa6af6cdde', - 'height': 0, - 'creativeId': '', - 'statusMessage': 'Bid returned empty or error response', - 'width': 0, - 'cpm': 0 - } - }; - let result = spec.interpretResponse(response, bidRequest[0]); - expect(result.length).to.equal(0); - }); - }); -}); diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index f6e24d07c63..91f900d6279 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -1,5 +1,5 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/colossussspBidAdapter.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/colossussspBidAdapter.js'; describe('ColossussspAdapter', function () { let bid = { @@ -7,7 +7,8 @@ describe('ColossussspAdapter', function () { bidder: 'colossusssp', bidderRequestId: '145e1d6a7837c9', params: { - placement_id: 0 + placement_id: 0, + group_id: 0 }, placementCode: 'placementid_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', @@ -16,6 +17,13 @@ describe('ColossussspAdapter', function () { sizes: [[300, 250]] } }, + ortb2Imp: { + ext: { + data: { + pbadslot: '/19968336/prebid_cache_video_adunit' + } + } + }, transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', schain: { ver: '1.0', @@ -53,6 +61,7 @@ describe('ColossussspAdapter', function () { }); it('Should return false when placement_id is not a number', function () { bid.params.placement_id = 'aaa'; + delete bid.params.group_id; expect(spec.isBidRequestValid(bid)).to.be.false; }); }); @@ -71,7 +80,7 @@ describe('ColossussspAdapter', function () { it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://colossusssp.com/?c=o&m=multi'); }); - it('Should contain ccpa', function() { + it('Should contain ccpa', function () { expect(serverRequest.data.ccpa).to.be.an('string') }) @@ -88,13 +97,15 @@ describe('ColossussspAdapter', function () { let placements = data['placements']; for (let i = 0; i < placements.length; i++) { let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'floor'); + expect(placement).to.have.all.keys('placementId', 'groupId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'floor', 'gpid'); expect(placement.schain).to.be.an('object') expect(placement.placementId).to.be.a('number'); + expect(placement.groupId).to.be.a('number'); expect(placement.bidId).to.be.a('string'); expect(placement.traffic).to.be.a('string'); expect(placement.sizes).to.be.an('array'); expect(placement.floor).to.be.an('object'); + expect(placement.gpid).to.be.an('string'); } }); it('Returns empty data if no valid requests are passed', function () { @@ -110,6 +121,7 @@ describe('ColossussspAdapter', function () { bid.userId.idl_env = 'idl_env123'; bid.userId.tdid = 'tdid123'; bid.userId.id5id = { uid: 'id5id123' }; + bid.userId.uid2 = { id: 'uid2id123' }; let serverRequest = spec.buildRequests([bid], bidderRequest); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; @@ -119,11 +131,11 @@ describe('ColossussspAdapter', function () { let placement = placements[i]; expect(placement).to.have.property('eids') expect(placement.eids).to.be.an('array') - expect(placement.eids.length).to.be.equal(4) + expect(placement.eids.length).to.be.equal(5) for (let index in placement.eids) { let v = placement.eids[index]; expect(v).to.have.all.keys('source', 'uids') - expect(v.source).to.be.oneOf(['britepool.com', 'identityLink', 'adserver.org', 'id5-sync.com']) + expect(v.source).to.be.oneOf(['britepool.com', 'identityLink', 'adserver.org', 'id5-sync.com', 'uidapi.com']) expect(v.uids).to.be.an('array'); expect(v.uids.length).to.be.equal(1) expect(v.uids[0]).to.have.property('id') @@ -134,7 +146,7 @@ describe('ColossussspAdapter', function () { describe('interpretResponse', function () { let resObject = { - body: [ { + body: [{ requestId: '123', mediaType: 'banner', cpm: 0.3, @@ -144,8 +156,12 @@ describe('ColossussspAdapter', function () { ttl: 1000, creativeId: '123asd', netRevenue: true, - currency: 'USD' - } ] + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] }; let serverResponses = spec.interpretResponse(resObject); it('Returns an array of valid server responses if response object is valid', function () { @@ -153,7 +169,7 @@ describe('ColossussspAdapter', function () { for (let i = 0; i < serverResponses.length; i++) { let dataItem = serverResponses[i]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType'); + 'netRevenue', 'currency', 'mediaType', 'meta'); expect(dataItem.requestId).to.be.a('string'); expect(dataItem.cpm).to.be.a('number'); expect(dataItem.width).to.be.a('number'); @@ -164,6 +180,7 @@ describe('ColossussspAdapter', function () { expect(dataItem.netRevenue).to.be.a('boolean'); expect(dataItem.currency).to.be.a('string'); expect(dataItem.mediaType).to.be.a('string'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); } it('Returns an empty array if invalid response is passed', function () { serverResponses = spec.interpretResponse('invalid_response'); @@ -172,6 +189,15 @@ describe('ColossussspAdapter', function () { }); }); + describe('onBidWon', function () { + it('should make an ajax call', function () { + const bid = { + nurl: 'http://example.com/win', + }; + expect(spec.onBidWon(bid)).to.equals(undefined); + }); + }) + describe('getUserSyncs', function () { let userSync = spec.getUserSyncs(); it('Returns valid URL and type', function () { diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js similarity index 53% rename from test/spec/modules/mobfoxpbBidAdapter_spec.js rename to test/spec/modules/compassBidAdapter_spec.js index a02d580ab88..28021c4f7c0 100644 --- a/test/spec/modules/mobfoxpbBidAdapter_spec.js +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -1,121 +1,172 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/mobfoxpbBidAdapter.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/compassBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; -describe('MobfoxHBBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'mobfoxpb', +const bidder = 'compass' + +describe('CompassBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, params: { - placementId: 783, - traffic: BANNER + } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { - referer: 'test.com' + referer: 'https://test.com' } }; describe('isBidRequestValid', function () { it('Should return true if there are bidId, params and key parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://bes.mobfox.com/?c=o&m=multi'); + expect(serverRequest.url).to.equal('https://sa-lb.deliverimp.com/pbjs'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain'); - expect(placement.placementId).to.equal(783); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal(BANNER); - expect(placement.schain).to.be.an('object'); - expect(placement.sizes).to.be.an('array'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.params.traffic = VIDEO; - bid.mediaTypes[VIDEO] = { - playerSize - }; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain'); - expect(placement.traffic).to.equal(VIDEO); - expect(placement.wPlayer).to.equal(playerSize[0]); - expect(placement.hPlayer).to.equal(playerSize[1]); - }); + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); - it('Returns valid data for mediatype native', function () { - const native = { - title: { - required: true - }, - body: { - required: true - }, - icon: { - required: true, - size: [64, 64] + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); } - }; - - bid.mediaTypes = {}; - bid.params.traffic = NATIVE; - bid.mediaTypes[NATIVE] = native; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'native', 'schain'); - expect(placement.traffic).to.equal(NATIVE); - expect(placement.native).to.equal(native); + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; expect(data.gdpr).to.be.a('string'); @@ -125,8 +176,9 @@ describe('MobfoxHBBidAdapter', function () { }); it('Returns data with uspConsent and without gdprConsent', function () { - bidderRequest.uspConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.ccpa).to.exist; expect(data.ccpa).to.be.a('string'); @@ -135,11 +187,12 @@ describe('MobfoxHBBidAdapter', function () { }); it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + serverRequest = spec.buildRequests([], bidderRequest); let data = serverRequest.data; expect(data.placements).to.be.an('array').that.is.empty; }); }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -154,23 +207,28 @@ describe('MobfoxHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); expect(bannerResponses).to.be.an('array').that.is.not.empty; let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { const video = { @@ -183,7 +241,11 @@ describe('MobfoxHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -191,7 +253,7 @@ describe('MobfoxHBBidAdapter', function () { let dataItem = videoResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.5); expect(dataItem.vastUrl).to.equal('test.com'); @@ -199,6 +261,7 @@ describe('MobfoxHBBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret native response', function () { const native = { @@ -216,13 +279,17 @@ describe('MobfoxHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); expect(nativeResponses).to.be.an('array').that.is.not.empty; let dataItem = nativeResponses[0]; - expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.4); @@ -235,6 +302,7 @@ describe('MobfoxHBBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should return an empty array if invalid banner response is passed', function () { const invBanner = { @@ -301,4 +369,30 @@ describe('MobfoxHBBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sa-cs.deliverimp.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sa-cs.deliverimp.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + }); }); diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js new file mode 100644 index 00000000000..41135a74515 --- /dev/null +++ b/test/spec/modules/connectIdSystem_spec.js @@ -0,0 +1,189 @@ +import {expect} from 'chai'; +import {parseQS} from 'src/utils.js'; +import {connectIdSubmodule} from 'modules/connectIdSystem.js'; + +describe('Yahoo ConnectID Submodule', () => { + const HASHED_EMAIL = '6bda6f2fa268bf0438b5423a9861a2cedaa5dec163c03f743cfe05c08a8397b2'; + const PIXEL_ID = '1234'; + const PROD_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PIXEL_ID}/fed`; + const OVERRIDE_ENDPOINT = 'https://foo/bar'; + + it('should have the correct module name declared', () => { + expect(connectIdSubmodule.name).to.equal('connectId'); + }); + + it('should have the correct TCFv2 Vendor ID declared', () => { + expect(connectIdSubmodule.gvlid).to.equal(25); + }); + + describe('getId()', () => { + let ajaxStub; + let getAjaxFnStub; + let consentData; + beforeEach(() => { + ajaxStub = sinon.stub(); + getAjaxFnStub = sinon.stub(connectIdSubmodule, 'getAjaxFn'); + getAjaxFnStub.returns(ajaxStub); + + consentData = { + gdpr: { + gdprApplies: 1, + consentString: 'GDPR_CONSENT_STRING' + }, + uspConsent: 'USP_CONSENT_STRING' + }; + }); + + afterEach(() => { + getAjaxFnStub.restore(); + }); + + function invokeGetIdAPI(configParams, consentData) { + let result = connectIdSubmodule.getId({ + params: configParams + }, consentData); + if (typeof result === 'object') { + result.callback(sinon.stub()); + } + return result; + } + + it('returns undefined if he and pixelId params are not passed', () => { + expect(invokeGetIdAPI({}, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); + + it('returns undefined if the pixelId param is not passed', () => { + expect(invokeGetIdAPI({ + he: HASHED_EMAIL + }, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); + + it('returns undefined if the he param is not passed', () => { + expect(invokeGetIdAPI({ + pixelId: PIXEL_ID + }, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); + + it('returns an object with the callback function if the correct params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('Makes an ajax GET request to the production API endpoint with query params', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.gdpr.consentString, + us_privacy: consentData.uspConsent + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the specified override API endpoint with query params', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.gdpr.consentString, + us_privacy: consentData.uspConsent + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${OVERRIDE_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('sets the callbacks param of the ajax function call correctly', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); + }); + + it('sets GDPR consent data flag correctly when call is under GDPR jurisdiction.', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('1'); + expect(requestQueryParams.gdpr_consent).to.equal(consentData.gdpr.consentString); + }); + + it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { + consentData.gdpr.gdprApplies = false; + + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('0'); + expect(requestQueryParams.gdpr_consent).to.equal(''); + }); + + [1, '1', true].forEach(firstPartyParamValue => { + it(`sets 1p payload property to '1' for a config value of ${firstPartyParamValue}`, () => { + invokeGetIdAPI({ + '1p': firstPartyParamValue, + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams['1p']).to.equal('1'); + }); + }); + }); + + describe('decode()', () => { + const VALID_API_RESPONSES = [{ + key: 'connectid', + expected: '4567', + payload: { + connectid: '4567' + } + }]; + VALID_API_RESPONSES.forEach(responseData => { + it('should return a newly constructed object with the connect ID for a payload with ${responseData.key} key(s)', () => { + expect(connectIdSubmodule.decode(responseData.payload)).to.deep.equal( + {connectId: responseData.expected} + ); + }); + }); + + [{}, '', {foo: 'bar'}].forEach((response) => { + it(`should return undefined for an invalid response "${JSON.stringify(response)}"`, () => { + expect(connectIdSubmodule.decode(response)).to.be.undefined; + }); + }); + }); +}); diff --git a/test/spec/modules/connectadBidAdapter_spec.js b/test/spec/modules/connectadBidAdapter_spec.js index dbac3c0dc7c..657bc432d06 100644 --- a/test/spec/modules/connectadBidAdapter_spec.js +++ b/test/spec/modules/connectadBidAdapter_spec.js @@ -303,7 +303,43 @@ describe('ConnectAd Adapter', function () { }); describe('bid responses', function () { - it('should return complete bid response', function () { + it('should return complete bid response with adomain', function () { + const ADOMAINS = ['connectad.io']; + + let serverResponse = { + body: { + decisions: { + '2f95c00074b931': { + adId: '0', + adomain: ['connectad.io'], + contents: [ + { + body: '<<<---- Creative --->>>' + } + ], + height: '250', + width: '300', + pricing: { + clearPrice: 11.899999999999999 + } + } + } + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].cpm).to.equal(11.899999999999999); + expect(bids[0].width).to.equal('300'); + expect(bids[0].height).to.equal('250'); + expect(bids[0].ad).to.have.length.above(1); + expect(bids[0].meta.advertiserDomains).to.deep.equal(ADOMAINS); + }); + + it('should return complete bid response with empty adomain', function () { + const ADOMAINS = []; + let serverResponse = { body: { decisions: { @@ -331,6 +367,7 @@ describe('ConnectAd Adapter', function () { expect(bids[0].width).to.equal('300'); expect(bids[0].height).to.equal('250'); expect(bids[0].ad).to.have.length.above(1); + expect(bids[0].meta.advertiserDomains).to.deep.equal(ADOMAINS); }); it('should return empty bid response', function () { diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index 7d3cd48a8e4..c3ee30afc7d 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -58,6 +58,12 @@ describe('consentManagement', function () { sinon.assert.notCalled(utils.logInfo); }); + it('should not produce any USP metadata', function() { + setConsentConfig({}); + let consentMeta = uspDataHandler.getConsentMeta(); + expect(consentMeta).to.be.undefined; + }); + it('should exit the consent manager if only config.gdpr is an object', function() { setConsentConfig({ gdpr: { cmpApi: 'iab' } }); expect(consentAPI).to.be.undefined; @@ -366,6 +372,27 @@ describe('consentManagement', function () { expect(didHookReturn).to.be.true; expect(consent).to.equal(testConsentData.uspString); }); + + it('returns USP consent metadata', function () { + let testConsentData = { + uspString: '1NY' + }; + + uspapiStub = sinon.stub(window, '__uspapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setConsentConfig(goodConfig); + requestBidsHook(() => { didHookReturn = true; }, {}); + + let consentMeta = uspDataHandler.getConsentMeta(); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + + expect(consentMeta.usp).to.equal(testConsentData.uspString); + expect(consentMeta.generatedAt).to.be.above(1644367751709); + }); }); }); }); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 5e9b0f07f46..52454937d68 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -45,6 +45,13 @@ describe('consentManagement', function () { expect(userCMP).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }); + + it('should not produce any consent metadata', function() { + setConsentConfig(undefined) + let consentMetadata = gdprDataHandler.getConsentMeta(); + expect(consentMetadata).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }) }); describe('valid setConsentConfig value', function () { @@ -667,6 +674,33 @@ describe('consentManagement', function () { expect(consent.apiVersion).to.equal(2); }); + it('produces gdpr metadata', function () { + let testConsentData = { + tcString: 'abc12345234', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded', + vendorData: { + tcString: 'abc12345234' + } + }; + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setConsentConfig(goodConfigWithAllowAuction); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consentMeta = gdprDataHandler.getConsentMeta(); + sinon.assert.notCalled(utils.logError); + expect(consentMeta.consentStringSize).to.be.above(0) + expect(consentMeta.gdprApplies).to.be.true; + expect(consentMeta.apiVersion).to.equal(2); + expect(consentMeta.generatedAt).to.be.above(1644367751709); + }); + it('performs lookup check and stores consentData for a valid existing user with additional consent', function () { let testConsentData = { tcString: 'abc12345234', @@ -715,6 +749,26 @@ describe('consentManagement', function () { expect(consent).to.be.null; }); + it('allows the auction when CMP is unresponsive', (done) => { + setConsentConfig({ + cmpApi: 'iab', + timeout: 10, + defaultGdprScope: true + }); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + + setTimeout(() => { + expect(didHookReturn).to.be.true; + const consent = gdprDataHandler.getConsentData(); + expect(consent.gdprApplies).to.be.true; + expect(consent.consentString).to.be.undefined; + done(); + }, 20); + }); + it('It still considers it a valid cmp response if gdprApplies is not a boolean', function () { // gdprApplies is undefined, should just still store consent response but use whatever defaultGdprScope was let testConsentData = { diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index 44076194885..f0b02913f96 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -299,6 +299,7 @@ describe('Consumable BidAdapter', function () { expect(b).to.have.property('currency', 'USD'); expect(b).to.have.property('creativeId'); expect(b).to.have.property('ttl', 30); + expect(b.meta).to.have.property('advertiserDomains'); expect(b).to.have.property('netRevenue', true); expect(b).to.have.property('referrer'); }); diff --git a/test/spec/modules/trendqubeBidAdapter_spec.js b/test/spec/modules/contentexchangeBidAdapter_spec.js similarity index 51% rename from test/spec/modules/trendqubeBidAdapter_spec.js rename to test/spec/modules/contentexchangeBidAdapter_spec.js index f2ce95832ff..368ca8d9e3f 100644 --- a/test/spec/modules/trendqubeBidAdapter_spec.js +++ b/test/spec/modules/contentexchangeBidAdapter_spec.js @@ -1,87 +1,174 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/trendqubeBidAdapter.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/contentexchangeBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; -describe('TrendqubebBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'trendqube', +const bidder = 'contentexchange' + +describe('ContentexchangeBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'test', + adFormat: BANNER + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'test', + adFormat: VIDEO + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'test', + adFormat: NATIVE + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, params: { - placementId: 0, - traffic: BANNER + adFormat: BANNER } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { - referer: 'test.com' + referer: 'https://test.com' } }; describe('isBidRequestValid', function () { - it('Should return true if there are bidId, params and placementId parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://ads.trendqube.com/?c=o&m=multi'); + expect(serverRequest.url).to.equal('https://eu2.adnetwork.agency/pbjs'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'hPlayer', 'wPlayer', 'schain'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal(BANNER); - expect(placement.schain).to.be.an('object'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.params.traffic = VIDEO; - bid.mediaTypes[VIDEO] = { - playerSize - }; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement.traffic).to.equal(VIDEO); - expect(placement.wPlayer).to.equal(playerSize[0]); - expect(placement.hPlayer).to.equal(playerSize[1]); + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.equal('test'); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; expect(data.gdpr).to.be.a('string'); @@ -91,8 +178,9 @@ describe('TrendqubebBidAdapter', function () { }); it('Returns data with uspConsent and without gdprConsent', function () { - bidderRequest.uspConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.ccpa).to.exist; expect(data.ccpa).to.be.a('string'); @@ -101,11 +189,12 @@ describe('TrendqubebBidAdapter', function () { }); it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + serverRequest = spec.buildRequests([], bidderRequest); let data = serverRequest.data; expect(data.placements).to.be.an('array').that.is.empty; }); }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -120,23 +209,28 @@ describe('TrendqubebBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); expect(bannerResponses).to.be.an('array').that.is.not.empty; let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { const video = { @@ -149,7 +243,11 @@ describe('TrendqubebBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -157,7 +255,7 @@ describe('TrendqubebBidAdapter', function () { let dataItem = videoResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.5); expect(dataItem.vastUrl).to.equal('test.com'); @@ -165,6 +263,7 @@ describe('TrendqubebBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret native response', function () { const native = { @@ -182,13 +281,17 @@ describe('TrendqubebBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); expect(nativeResponses).to.be.an('array').that.is.not.empty; let dataItem = nativeResponses[0]; - expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.4); @@ -201,6 +304,7 @@ describe('TrendqubebBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should return an empty array if invalid banner response is passed', function () { const invBanner = { @@ -267,4 +371,29 @@ describe('TrendqubebBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync2.adnetwork.agency/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync2.adnetwork.agency/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + }); }); diff --git a/test/spec/modules/convergeBidAdapter_spec.js b/test/spec/modules/convergeBidAdapter_spec.js deleted file mode 100644 index e92ed475497..00000000000 --- a/test/spec/modules/convergeBidAdapter_spec.js +++ /dev/null @@ -1,899 +0,0 @@ -import { expect } from 'chai'; -import { spec, resetUserSync, getSyncUrl } from 'modules/convergeBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -describe('ConvergeAdapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'converge', - 'params': { - 'uid': '1' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'uid': 0 - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - function parseRequest(url) { - const res = {}; - url.split('&').forEach((it) => { - const couple = it.split('='); - res[couple[0]] = decodeURIComponent(couple[1]); - }); - return res; - } - - const bidderRequest = { - refererInfo: { - referer: 'https://example.com' - } - }; - const referrer = bidderRequest.refererInfo.referer; - - let bidRequests = [ - { - 'bidder': 'converge', - 'params': { - 'uid': '59' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '59' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90], [300, 250]], - 'bidId': '3150ccb55da321', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '60' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '42dbe3a7168a6a', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - it('should attach valid params to the tag', function () { - const request = spec.buildRequests([bidRequests[0]], bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '59'); - expect(payload).to.have.property('sizes', '300x250,300x600'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - expect(payload).to.have.property('wrapperType', 'Prebid_js'); - expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); - }); - - it('sizes must not be duplicated', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '59,59,60'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - }); - - it('pt parameter must be "gross" if params.priceType === "gross"', function () { - bidRequests[1].params.priceType = 'gross'; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'gross'); - expect(payload).to.have.property('auids', '59,59,60'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - delete bidRequests[1].params.priceType; - }); - - it('pt parameter must be "net" or "gross"', function () { - bidRequests[1].params.priceType = 'some'; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '59,59,60'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - delete bidRequests[1].params.priceType; - }); - - it('if gdprConsent is present payload must have gdpr params', function () { - const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '1'); - }); - - it('if gdprApplies is false gdpr_applies must be 0', function () { - const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: false}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '0'); - }); - - it('if gdprApplies is undefined gdpr_applies must be 1', function () { - const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA'}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '1'); - }); - - it('if usPrivacy is present payload must have us_privacy param', function () { - const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('us_privacy', '1YNN'); - }); - - it('should convert keyword params to proper form and attaches to request', function () { - const bidRequestWithKeywords = [].concat(bidRequests); - bidRequestWithKeywords[1] = Object.assign({}, - bidRequests[1], - { - params: { - uid: '59', - keywords: { - single: 'val', - singleArr: ['val'], - singleArrNum: [5], - multiValMixed: ['value1', 2, 'value3'], - singleValNum: 123, - emptyStr: '', - emptyArr: [''], - badValue: {'foo': 'bar'} // should be dropped - } - } - } - ); - - const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload.keywords).to.be.an('string'); - payload.keywords = JSON.parse(payload.keywords); - - expect(payload.keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'singleArrNum', - 'value': ['5'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'singleValNum', - 'value': ['123'] - }, { - 'key': 'emptyStr' - }, { - 'key': 'emptyArr' - }]); - }); - }); - - describe('interpretResponse', function () { - const responses = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 59, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 60, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 59, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'price': 0, 'auid': 61, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0, 'adm': '
test content 5
', 'h': 250, 'w': 300}], 'seat': '1'}, - undefined, - {'bid': [], 'seat': '1'}, - {'seat': '1'}, - ]; - - it('should get correct bid response', function () { - const bidRequests = [ - { - 'bidder': 'converge', - 'params': { - 'uid': '59' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '659423fff799cb', - 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', - } - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '659423fff799cb', - 'cpm': 1.15, - 'creativeId': 59, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 1
', - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); - expect(result).to.deep.equal(expectedResponse); - }); - - it('should get correct multi bid response', function () { - const bidRequests = [ - { - 'bidder': 'converge', - 'params': { - 'uid': '59' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '300bfeb0d71a5b', - 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '60' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4dff80cc4ee346', - 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '59' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'bidId': '5703af74d0472a', - 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', - } - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '300bfeb0d71a5b', - 'cpm': 1.15, - 'creativeId': 59, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 1
', - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '4dff80cc4ee346', - 'cpm': 0.5, - 'creativeId': 60, - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'ad': '
test content 2
', - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '5703af74d0472a', - 'cpm': 0.15, - 'creativeId': 59, - 'dealId': undefined, - 'width': 728, - 'height': 90, - 'ad': '
test content 3
', - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': responses.slice(0, 3)}}, request); - expect(result).to.deep.equal(expectedResponse); - }); - - it('handles wrong and nobid responses', function () { - const bidRequests = [ - { - 'bidder': 'converge', - 'params': { - 'uid': '61' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '300bfeb0d7190gf', - 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '65' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '300bfeb0d71321', - 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '70' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'bidId': '300bfeb0d7183bb', - 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', - } - ]; - const request = spec.buildRequests(bidRequests); - const result = spec.interpretResponse({'body': {'seatbid': responses.slice(3)}}, request); - expect(result.length).to.equal(0); - }); - - it('complicated case', function () { - const fullResponse = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 59, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 60, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 59, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 4
', 'auid': 59, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 5
', 'auid': 60, 'h': 600, 'w': 350}], 'seat': '1'}, - ]; - const bidRequests = [ - { - 'bidder': 'converge', - 'params': { - 'uid': '59' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '2164be6358b9', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '59' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '326bde7fbf69', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '60' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4e111f1b66e4', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '59' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'bidId': '26d6f897b516', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '60' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'bidId': '1751cd90161', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - } - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '2164be6358b9', - 'cpm': 1.15, - 'creativeId': 59, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 1
', - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '4e111f1b66e4', - 'cpm': 0.5, - 'creativeId': 60, - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'ad': '
test content 2
', - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '26d6f897b516', - 'cpm': 0.15, - 'creativeId': 59, - 'dealId': undefined, - 'width': 728, - 'height': 90, - 'ad': '
test content 3
', - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '326bde7fbf69', - 'cpm': 0.15, - 'creativeId': 59, - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'ad': '
test content 4
', - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); - expect(result).to.deep.equal(expectedResponse); - }); - - it('dublicate uids and sizes in one slot', function () { - const fullResponse = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 59, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 59, 'h': 250, 'w': 300}], 'seat': '1'}, - ]; - const bidRequests = [ - { - 'bidder': 'converge', - 'params': { - 'uid': '59' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '5126e301f4be', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '59' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '57b2ebe70e16', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '59' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '225fcd44b18c', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', - } - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '5126e301f4be', - 'cpm': 1.15, - 'creativeId': 59, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 1
', - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '57b2ebe70e16', - 'cpm': 0.5, - 'creativeId': 59, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 2
', - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); - expect(result).to.deep.equal(expectedResponse); - }); - }); - - it('should get correct video bid response', function () { - const bidRequests = [ - { - 'bidder': 'converge', - 'params': { - 'uid': '58' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '57dfefb80eca', - 'bidderRequestId': '20394420a762a2', - 'auctionId': '140132d07b031', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '60' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'e893c787c22dd', - 'bidderRequestId': '20394420a762a2', - 'auctionId': '140132d07b031', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - } - ]; - const response = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 58, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, - {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 60, content_type: 'video'}], 'seat': '2'} - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '57dfefb80eca', - 'cpm': 1.15, - 'creativeId': 58, - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - } - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': response}}, request); - expect(result).to.deep.equal(expectedResponse); - }); - - it('should have right renderer in the bid response', function () { - const spySetRenderer = sinon.spy(); - const stubRenderer = { - setRender: spySetRenderer - }; - const spyRendererInstall = sinon.spy(function() { return stubRenderer; }); - const stubRendererConst = { - install: spyRendererInstall - }; - const bidRequests = [ - { - 'bidder': 'converge', - 'params': { - 'uid': '58' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'e6e65553fc8', - 'bidderRequestId': '1380f393215dc7', - 'auctionId': '10b8d2f3c697e3', - 'mediaTypes': { - 'video': { - 'context': 'outstream' - } - } - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '60' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c8fdcb3f269f', - 'bidderRequestId': '1380f393215dc7', - 'auctionId': '10b8d2f3c697e3' - }, - { - 'bidder': 'converge', - 'params': { - 'uid': '61' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '1de036c37685', - 'bidderRequestId': '1380f393215dc7', - 'auctionId': '10b8d2f3c697e3', - 'renderer': {} - } - ]; - const response = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 58, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, - {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 60, content_type: 'video', w: 300, h: 250}], 'seat': '2'}, - {'bid': [{'price': 1.20, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 61, content_type: 'video', w: 300, h: 250}], 'seat': '2'} - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': 'e6e65553fc8', - 'cpm': 1.15, - 'creativeId': 58, - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - }, - 'renderer': stubRenderer - }, - { - 'requestId': 'c8fdcb3f269f', - 'cpm': 1.00, - 'creativeId': 60, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - }, - 'renderer': stubRenderer - }, - { - 'requestId': '1de036c37685', - 'cpm': 1.20, - 'creativeId': 61, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'bidderCode': 'converge', - 'currency': 'EUR', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - } - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': response}}, request, stubRendererConst); - - expect(spySetRenderer.calledTwice).to.equal(true); - expect(spySetRenderer.getCall(0).args[0]).to.be.a('function'); - expect(spySetRenderer.getCall(1).args[0]).to.be.a('function'); - - expect(spyRendererInstall.calledTwice).to.equal(true); - expect(spyRendererInstall.getCall(0).args[0]).to.deep.equal({ - id: 'e6e65553fc8', - url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', - loaded: false - }); - expect(spyRendererInstall.getCall(1).args[0]).to.deep.equal({ - id: 'c8fdcb3f269f', - url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', - loaded: false - }); - - expect(result).to.deep.equal(expectedResponse); - }); - - describe('user sync', function () { - const syncUrl = getSyncUrl(); - - beforeEach(function () { - resetUserSync(); - }); - - it('should register sync image', function () { - let syncs = spec.getUserSyncs({ - pixelEnabled: true - }); - - expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); - }); - - it('should not register sync image more than once', function () { - let syncs = spec.getUserSyncs({ - pixelEnabled: true - }); - expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); - - // when called again, should still have only been called once - syncs = spec.getUserSyncs(); - expect(syncs).to.equal(undefined); - }); - - it('should pass gdpr params if consent is true', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - gdprApplies: true, consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr=1&gdpr_consent=foo` - }); - }); - - it('should pass gdpr params if consent is false', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - gdprApplies: false, consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr=0&gdpr_consent=foo` - }); - }); - - it('should pass gdpr param gdpr_consent only when gdprApplies is undefined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr_consent=foo` - }); - }); - - it('should pass no params if gdpr consentString is not defined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {})).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is a number', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: 0 - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is null', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: null - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is a object', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: {} - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr is not defined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined)).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass usPrivacy param if it is available', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {}, '1YNN')).to.deep.equal({ - type: 'image', url: `${syncUrl}&us_privacy=1YNN` - }); - }); - }); -}); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 1c24fb7694a..bc5cbd4134b 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -1,7 +1,8 @@ import {expect} from 'chai'; import {spec, storage} from 'modules/conversantBidAdapter.js'; import * as utils from 'src/utils.js'; -import { createEidsArray } from 'modules/userId/eids.js'; +import {createEidsArray} from 'modules/userId/eids.js'; +import { config } from '../../../src/config.js'; describe('Conversant adapter tests', function() { const siteId = '108060'; @@ -119,7 +120,34 @@ describe('Conversant adapter tests', function() { bidId: 'bid005', bidderRequestId: '117d765b87bed38', auctionId: 'req000' - }]; + }, + // video with first party data + { + bidder: 'conversant', + params: { + site_id: siteId + }, + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mp4', 'video/x-flv'] + } + }, + ortb2Imp: { + instl: 1, + ext: { + data: { + pbadslot: 'homepage-top-rect' + } + } + }, + placementCode: 'pcode006', + transactionId: 'tx006', + bidId: 'bid006', + bidderRequestId: '117d765b87bed38', + auctionId: 'req000' + } + ]; const bidResponses = { body: { @@ -216,7 +244,7 @@ describe('Conversant adapter tests', function() { expect(payload).to.have.property('id', 'req000'); expect(payload).to.have.property('at', 1); expect(payload).to.have.property('imp'); - expect(payload.imp).to.be.an('array').with.lengthOf(6); + expect(payload.imp).to.be.an('array').with.lengthOf(7); expect(payload.imp[0]).to.have.property('id', 'bid000'); expect(payload.imp[0]).to.have.property('secure', 1); @@ -306,6 +334,16 @@ describe('Conversant adapter tests', function() { expect(payload.imp[5].video).to.not.have.property('maxduration'); expect(payload.imp[5]).to.not.have.property('banner'); + expect(payload.imp[6]).to.have.property('id', 'bid006'); + expect(payload.imp[6]).to.have.property('video'); + expect(payload.imp[6].video).to.have.property('mimes'); + expect(payload.imp[6].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(payload.imp[6]).to.not.have.property('banner'); + expect(payload.imp[6]).to.have.property('instl'); + expect(payload.imp[6]).to.have.property('ext'); + expect(payload.imp[6].ext).to.have.property('data'); + expect(payload.imp[6].ext.data).to.have.property('pbadslot'); + expect(payload).to.have.property('site'); expect(payload.site).to.have.property('id', siteId); expect(payload.site).to.have.property('mobile').that.is.oneOf([0, 1]); @@ -321,6 +359,20 @@ describe('Conversant adapter tests', function() { expect(payload).to.not.have.property('user'); // there should be no user by default }); + it('Verify first party data', () => { + const bidderRequest = {refererInfo: {referer: 'http://test.com?a=b&c=123'}}; + const cfg = {ortb2: {site: {content: {series: 'MySeries', season: 'MySeason', episode: 3, title: 'MyTitle'}}}}; + config.setConfig(cfg); + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + expect(payload.site).to.have.property('content'); + expect(payload.site.content).to.have.property('series'); + expect(payload.site.content).to.have.property('season'); + expect(payload.site.content).to.have.property('episode'); + expect(payload.site.content).to.have.property('title'); + config.resetConfig(); + }); + it('Verify override url', function() { const testUrl = 'https://someurl?name=value'; const request = spec.buildRequests([{params: {white_label_url: testUrl}}]); @@ -588,4 +640,133 @@ describe('Conversant adapter tests', function() { expect(payload).to.have.deep.nested.property('user.ext.fpc', 'fghijk'); }); }); + + describe('price floor module', function() { + let bidRequest; + beforeEach(function() { + bidRequest = [utils.deepClone(bidRequests[0])]; + delete bidRequest[0].params.bidfloor; + }); + + it('obtain floor from getFloor', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'USD', + floor: 3.21 + }; + }; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 3.21); + }); + + it('obtain floor from params', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'USD', + floor: 3.21 + }; + }; + bidRequest[0].params.bidfloor = 0.6; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 0.6); + }); + + it('unsupported currency', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'EUR', + floor: 1.23 + }; + }; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 0); + }); + + it('bad floor value', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'USD', + floor: 'test' + }; + }; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 0); + }); + + it('empty floor object', function() { + bidRequest[0].getFloor = () => { + return {}; + }; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 0); + }); + + it('undefined floor result', function() { + bidRequest[0].getFloor = () => {}; + + const payload = spec.buildRequests(bidRequest).data; + expect(payload.imp[0]).to.have.property('bidfloor', 0); + }); + }); + + describe('getUserSyncs', function() { + const syncurl_iframe = 'https://sync.dotomi.com:8080/iframe'; + const syncurl_image = 'https://sync.dotomi.com:8080/pixel'; + const cnvrResponse = {ext: {psyncs: [syncurl_image], fsyncs: [syncurl_iframe]}}; + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function() { + sandbox.restore(); + }); + + it('empty params', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)) + .to.deep.equal([]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {ext: {}}, undefined, undefined)) + .to.deep.equal([]); + expect(spec.getUserSyncs({ iframeEnabled: true }, cnvrResponse, undefined, undefined)) + .to.deep.equal([{ type: 'iframe', url: syncurl_iframe }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, cnvrResponse, undefined, undefined)) + .to.deep.equal([{ type: 'image', url: syncurl_image }]); + expect(spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, cnvrResponse, undefined, undefined)) + .to.deep.equal([{type: 'iframe', url: syncurl_iframe}, {type: 'image', url: syncurl_image}]); + }); + + it('URL building', function() { + expect(spec.getUserSyncs({pixelEnabled: true}, {ext: {psyncs: [`${syncurl_image}?sid=1234`]}}, undefined, undefined)) + .to.deep.equal([{type: 'image', url: `${syncurl_image}?sid=1234`}]); + expect(spec.getUserSyncs({pixelEnabled: true}, {ext: {psyncs: [`${syncurl_image}?sid=1234`]}}, undefined, '1NYN')) + .to.deep.equal([{type: 'image', url: `${syncurl_image}?sid=1234&us_privacy=1NYN`}]); + }); + + it('GDPR', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, cnvrResponse, {gdprApplies: true, consentString: 'consentstring'}, undefined)) + .to.deep.equal([{ type: 'iframe', url: `${syncurl_iframe}?gdpr=1&gdpr_consent=consentstring` }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, cnvrResponse, {gdprApplies: false, consentString: 'consentstring'}, undefined)) + .to.deep.equal([{ type: 'iframe', url: `${syncurl_iframe}?gdpr=0&gdpr_consent=consentstring` }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, cnvrResponse, {gdprApplies: true, consentString: undefined}, undefined)) + .to.deep.equal([{ type: 'iframe', url: `${syncurl_iframe}?gdpr=1&gdpr_consent=` }]); + + expect(spec.getUserSyncs({ pixelEnabled: true }, cnvrResponse, {gdprApplies: true, consentString: 'consentstring'}, undefined)) + .to.deep.equal([{ type: 'image', url: `${syncurl_image}?gdpr=1&gdpr_consent=consentstring` }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, cnvrResponse, {gdprApplies: false, consentString: 'consentstring'}, undefined)) + .to.deep.equal([{ type: 'image', url: `${syncurl_image}?gdpr=0&gdpr_consent=consentstring` }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, cnvrResponse, {gdprApplies: true, consentString: undefined}, undefined)) + .to.deep.equal([{ type: 'image', url: `${syncurl_image}?gdpr=1&gdpr_consent=` }]); + }); + + it('US_Privacy', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, cnvrResponse, undefined, '1NYN')) + .to.deep.equal([{ type: 'iframe', url: `${syncurl_iframe}?us_privacy=1NYN` }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, cnvrResponse, undefined, '1NYN')) + .to.deep.equal([{ type: 'image', url: `${syncurl_image}?us_privacy=1NYN` }]); + }); + }); }); diff --git a/test/spec/modules/cosmosBidAdapter_spec.js b/test/spec/modules/cosmosBidAdapter_spec.js deleted file mode 100644 index b33f53221e2..00000000000 --- a/test/spec/modules/cosmosBidAdapter_spec.js +++ /dev/null @@ -1,355 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/cosmosBidAdapter.js'; -import * as utils from 'src/utils.js'; -const constants = require('src/constants.json'); - -describe('Cosmos adapter', function () { - let bannerBidRequests; - let bannerBidResponse; - let videoBidRequests; - let videoBidResponse; - - beforeEach(function () { - bannerBidRequests = [ - { - bidder: 'cosmos', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - } - }, - params: { - publisherId: '1001', - currency: 'USD', - geo: { - lat: '09.5', - lon: '21.2', - } - }, - bidId: '29f8bd96defe76' - } - ]; - - videoBidRequests = - [ - { - mediaTypes: { - video: { - mimes: ['video/mp4', 'video/x-flv'], - context: 'instream' - } - }, - bidder: 'cosmos', - params: { - publisherId: 1001, - video: { - skippable: true, - minduration: 5, - maxduration: 30 - } - }, - bidId: '39f5cc6eff9b37' - } - ]; - - bannerBidResponse = { - 'body': { - 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', - 'seatbid': [{ - 'bid': [{ - 'id': '82DAAE22-FF66-4FAB-84AB-347B0C5CD02C', - 'impid': '29f8bd96defe76', - 'price': 1.858309, - 'adm': '

COSMOS\"Connecting Advertisers and Publishers directly\"

', - 'adid': 'v55jutrh', - 'adomain': ['febreze.com'], - 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', - 'cid': '1234', - 'crid': 'v55jutrh', - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - } - } - }], - 'seat': 'zeta' - }] - } - }; - - videoBidResponse = { - 'body': { - 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', - 'seatbid': [{ - 'bid': [{ - 'id': '82DAAE22-FF66-4FAB-84AB-347B0C5CD02C', - 'impid': '39f5cc6eff9b37', - 'price': 0.858309, - 'adm': 'CosmosHQVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://track.cosmoshq.com/event?data=%7B%22id%22%3A%221566011421045%22%2C%22bid%22%3A%2282DAAE22-FF66-4FAB-84AB-347B0C5CD02C%22%2C%22ts%22%3A%2220190817031021%22%2C%22pid%22%3A1001%2C%22plcid%22%3A1%2C%22aid%22%3A1%2C%22did%22%3A1%2C%22cid%22%3A%2222918%22%2C%22af%22%3A3%2C%22at%22%3A1%2C%22w%22%3A300%2C%22h%22%3A250%2C%22crid%22%3A%22v55jutrh%22%2C%22pp%22%3A0.858309%2C%22cp%22%3A0.858309%2C%22mg%22%3A0%7D&type=1https//track.dsp.impression.com/impression00:00:60https//sync.cosmoshq.com/static/video/SampleVideo_1280x720_10mb.mp4', - 'adid': 'v55jutrh', - 'adomain': ['febreze.com'], - 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', - 'cid': '1234', - 'crid': 'v55jutrh', - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'video' - } - } - }], - 'seat': 'zeta' - }] - } - }; - }); - - describe('isBidRequestValid', function () { - describe('validate the bid object: valid bid', function () { - it('valid bid case', function () { - let validBid = { - bidder: 'cosmos', - params: { - publisherId: 1001, - tagId: 1 - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); - }); - - it('validate the bid object: nil/empty bid object', function () { - let validBid = { - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }); - - it('validate the bid object: publisherId not passed', function () { - let validBid = { - bidder: 'cosmos', - params: { - tagId: 1 - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }); - - it('validate the bid object: publisherId is not number', function () { - let validBid = { - bidder: 'cosmos', - params: { - publisherId: '301', - tagId: 1 - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }); - - it('validate the bid object: mimes absent', function () { - let validBid = { - bidder: 'cosmos', - mediaTypes: { - video: {} - }, - params: { - publisherId: 1001 - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }); - - it('validate the bid object: mimes present', function () { - let validBid = { - bidder: 'cosmos', - mediaTypes: { - video: { - mimes: ['video/mp4', 'application/javascript'] - } - }, - params: { - publisherId: 1001 - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); - }); - - it('validate the bid object: tagId is not passed', function () { - let validBid = { - bidder: 'cosmos', - params: { - publisherId: 1001 - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); - }); - }); - - describe('buildRequests', function () { - it('build request object: buildRequests function should not modify original bannerBidRequests object', function () { - let originalBidRequests = utils.deepClone(bannerBidRequests); - let request = spec.buildRequests(bannerBidRequests); - expect(bannerBidRequests).to.deep.equal(originalBidRequests); - }); - - it('build request object: endpoint check', function () { - let request = spec.buildRequests(bannerBidRequests); - expect(request[0].url).to.equal('https://bid.cosmoshq.com/openrtb2/bids'); - expect(request[0].method).to.equal('POST'); - }); - - it('build request object: request params check', function () { - let request = spec.buildRequests(bannerBidRequests); - let data = JSON.parse(request[0].data); - expect(data.site.publisher.id).to.equal(bannerBidRequests[0].params.publisherId); // publisher Id - expect(data.imp[0].bidfloorcur).to.equal(bannerBidRequests[0].params.currency); - }); - - it('build request object: request params check without tagId', function () { - delete bannerBidRequests[0].params.tagId; - let request = spec.buildRequests(bannerBidRequests); - let data = JSON.parse(request[0].data); - expect(data.site.publisher.id).to.equal(bannerBidRequests[0].params.publisherId); // publisher Id - expect(data.imp[0].tagid).to.equal(undefined); // tagid - expect(data.imp[0].bidfloorcur).to.equal(bannerBidRequests[0].params.currency); - }); - - it('build request object: request params multi size format object check', function () { - let bidRequest = [ - { - bidder: 'cosmos', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - } - }, - params: { - publisherId: 1001, - currency: 'USD' - } - } - ]; - /* case 1 - size passed in adslot */ - let request = spec.buildRequests(bidRequest); - let data = JSON.parse(request[0].data); - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - - /* case 2 - size passed in adslot as well as in sizes array */ - bidRequest[0].sizes = [[300, 600], [300, 250]]; - bidRequest[0].mediaTypes = { - banner: { - sizes: [[300, 600], [300, 250]] - } - }; - request = spec.buildRequests(bidRequest); - data = JSON.parse(request[0].data); - - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(600); // height - - /* case 3 - size passed in sizes but not in adslot */ - bidRequest[0].params.tagId = 1; - bidRequest[0].sizes = [[300, 250], [300, 600]]; - bidRequest[0].mediaTypes = { - banner: { - sizes: [[300, 250], [300, 600]] - } - }; - request = spec.buildRequests(bidRequest); - data = JSON.parse(request[0].data); - - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - expect(data.imp[0].banner.format).exist.and.to.be.an('array'); - expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); - expect(data.imp[0].banner.format[0].w).to.equal(300); // width - expect(data.imp[0].banner.format[0].h).to.equal(250); // height - }); - - it('build request object: request params currency check', function () { - let bidRequest = [ - { - bidder: 'cosmos', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - } - }, - params: { - publisherId: 1001, - tagId: 1, - currency: 'USD' - }, - sizes: [[300, 250], [300, 600]] - } - ]; - - /* case 1 - - currency specified in adunits - output: imp[0] use currency specified in bannerBidRequests[0].params.currency - - */ - let request = spec.buildRequests(bidRequest); - let data = JSON.parse(request[0].data); - expect(data.imp[0].bidfloorcur).to.equal(bidRequest[0].params.currency); - - /* case 2 - - currency specified in adunit - output: imp[0] use default currency - USD - - */ - delete bidRequest[0].params.currency; - request = spec.buildRequests(bidRequest); - data = JSON.parse(request[0].data); - expect(data.imp[0].bidfloorcur).to.equal('USD'); - }); - - it('build request object: request params check for video ad', function () { - let request = spec.buildRequests(videoBidRequests); - let data = JSON.parse(request[0].data); - expect(data.imp[0].video).to.exist; - expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].mediaTypes.video['mimes'][0]); - expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].mediaTypes.video['mimes'][1]); - expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); - expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); - }); - - describe('interpretResponse', function () { - it('check for banner response', function () { - let request = spec.buildRequests(bannerBidRequests); - let data = JSON.parse(request[0].data); - let response = spec.interpretResponse(bannerBidResponse, request[0]); - expect(response).to.be.an('array').with.length.above(0); - expect(response[0].requestId).to.equal(bannerBidResponse.body.seatbid[0].bid[0].impid); - expect(response[0].cpm).to.equal((bannerBidResponse.body.seatbid[0].bid[0].price).toFixed(2)); - expect(response[0].width).to.equal(bannerBidResponse.body.seatbid[0].bid[0].w); - expect(response[0].height).to.equal(bannerBidResponse.body.seatbid[0].bid[0].h); - if (bannerBidResponse.body.seatbid[0].bid[0].crid) { - expect(response[0].creativeId).to.equal(bannerBidResponse.body.seatbid[0].bid[0].crid); - } else { - expect(response[0].creativeId).to.equal(bannerBidResponse.body.seatbid[0].bid[0].id); - } - expect(response[0].dealId).to.equal(bannerBidResponse.body.seatbid[0].bid[0].dealid); - expect(response[0].currency).to.equal('USD'); - expect(response[0].netRevenue).to.equal(false); - expect(response[0].ttl).to.equal(300); - }); - it('check for video response', function () { - let request = spec.buildRequests(videoBidRequests); - let data = JSON.parse(request[0].data); - let response = spec.interpretResponse(videoBidResponse, request[0]); - }); - }); - }); - }); -}); diff --git a/test/spec/modules/cpmstarBidAdapter_spec.js b/test/spec/modules/cpmstarBidAdapter_spec.js old mode 100644 new mode 100755 diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index cad1e3f8114..1e5f9e613d2 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1,5 +1,11 @@ import { expect } from 'chai'; -import { tryGetCriteoFastBid, spec, PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION } from 'modules/criteoBidAdapter.js'; +import { + tryGetCriteoFastBid, + spec, + PROFILE_ID_PUBLISHERTAG, + ADAPTER_VERSION, + canFastBid, getFastBidUrl, FAST_BID_VERSION_CURRENT +} from 'modules/criteoBidAdapter.js'; import { createBid } from 'src/bidfactory.js'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; @@ -66,7 +72,7 @@ describe('The Criteo bidding adapter', function () { expect(isValid).to.equal(true); }); - it('should return true when given a valid video bid request', function () { + it('should return true when given a valid video bid request using mix custom bidder video parameters', function () { expect(spec.isBidRequestValid({ bidder: 'criteo', mediaTypes: { @@ -112,6 +118,30 @@ describe('The Criteo bidding adapter', function () { })).to.equal(true); }); + it('should return true when given a valid video bid request using only mediaTypes.video parameters', function () { + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2], + skip: 1, + placement: 1, + minduration: 0, + playbackmethod: 1, + startdelay: 0 + } + }, + params: { + networkId: 456 + }, + })).to.equal(true); + }); + it('should return false when given an invalid video bid request', function () { expect(spec.isBidRequestValid({ bidder: 'criteo', @@ -633,6 +663,27 @@ describe('The Criteo bidding adapter', function () { expect(request.data.user.uspIab).to.equal('1YNY'); }); + it('should properly build a request with schain object', function () { + const expectedSchain = { + someProperty: 'someValue' + }; + const bidRequests = [ + { + bidder: 'criteo', + schain: expectedSchain, + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], + params: { + zoneId: 123, + }, + }, + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.source.ext.schain).to.equal(expectedSchain); + }); + it('should properly build a request with if ccpa consent field is not provided', function () { const bidRequests = [ { @@ -742,6 +793,44 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.slots[0].video.placement).to.equal(2); }); + it('should properly build a video request when mediaTypes.video.skip=0', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], + mediaTypes: { + video: { + playerSize: [ [300, 250] ], + mimes: ['video/mp4', 'video/MPV', 'video/H264', 'video/webm', 'video/ogg'], + minduration: 1, + maxduration: 30, + playbackmethod: [2, 3, 4, 5, 6], + api: [1, 2, 3, 4, 5, 6], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 0 + } + }, + params: { + networkId: 123 + } + } + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['300x250']); + expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/MPV', 'video/H264', 'video/webm', 'video/ogg']); + expect(ortbRequest.slots[0].video.minduration).to.equal(1); + expect(ortbRequest.slots[0].video.maxduration).to.equal(30); + expect(ortbRequest.slots[0].video.playbackmethod).to.deep.equal([2, 3, 4, 5, 6]); + expect(ortbRequest.slots[0].video.api).to.deep.equal([1, 2, 3, 4, 5, 6]); + expect(ortbRequest.slots[0].video.protocols).to.deep.equal([1, 2, 3, 4, 5, 6, 7, 8]); + expect(ortbRequest.slots[0].video.skip).to.equal(0); + }); + it('should properly build a request with ceh', function () { const bidRequests = [ { @@ -893,9 +982,11 @@ describe('The Criteo bidding adapter', function () { impid: 'test-requestId', cpm: 1.23, creative: 'test-ad', + creativecode: 'test-crId', width: 728, height: 90, dealCode: 'myDealCode', + adomain: ['criteo.com'], }], }, }; @@ -913,9 +1004,11 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].requestId).to.equal('test-bidId'); expect(bids[0].cpm).to.equal(1.23); expect(bids[0].ad).to.equal('test-ad'); + expect(bids[0].creativeId).to.equal('test-crId'); expect(bids[0].width).to.equal(728); expect(bids[0].height).to.equal(90); expect(bids[0].dealId).to.equal('myDealCode'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('criteo.com'); }); it('should properly parse a bid response with a zoneId', function () { @@ -1189,6 +1282,34 @@ describe('The Criteo bidding adapter', function () { }); }); + describe('canFastBid', function () { + it('should properly detect if can do fastbid', function () { + const testCasesAndExpectedResult = [['none', false], ['', true], [undefined, true], [123, true]]; + testCasesAndExpectedResult.forEach(testCase => { + const result = canFastBid(testCase[0]); + expect(result).to.equal(testCase[1]); + }) + }); + }); + + describe('getFastBidUrl', function () { + it('should properly detect the version of fastbid', function () { + const testCasesAndExpectedResult = [ + ['', 'https://static.criteo.net/js/ld/publishertag.prebid.' + FAST_BID_VERSION_CURRENT + '.js'], + [undefined, 'https://static.criteo.net/js/ld/publishertag.prebid.' + FAST_BID_VERSION_CURRENT + '.js'], + [null, 'https://static.criteo.net/js/ld/publishertag.prebid.' + FAST_BID_VERSION_CURRENT + '.js'], + [NaN, 'https://static.criteo.net/js/ld/publishertag.prebid.' + FAST_BID_VERSION_CURRENT + '.js'], + [123, 'https://static.criteo.net/js/ld/publishertag.prebid.123.js'], + ['123', 'https://static.criteo.net/js/ld/publishertag.prebid.123.js'], + ['latest', 'https://static.criteo.net/js/ld/publishertag.prebid.js'] + ]; + testCasesAndExpectedResult.forEach(testCase => { + const result = getFastBidUrl(testCase[0]); + expect(result).to.equal(testCase[1]); + }) + }); + }); + describe('tryGetCriteoFastBid', function () { const VALID_HASH = 'vBeD8Q7GU6lypFbzB07W8hLGj7NL+p7dI9ro2tCxkrmyv0F6stNuoNd75Us33iNKfEoW+cFWypelr6OJPXxki2MXWatRhJuUJZMcK4VBFnxi3Ro+3a0xEfxE4jJm4eGe98iC898M+/YFHfp+fEPEnS6pEyw124ONIFZFrcejpHU='; const INVALID_HASH = 'invalid'; diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index 65e5aaf741d..d50eadebb55 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -1,15 +1,9 @@ import { criteoIdSubmodule, storage } from 'modules/criteoIdSystem.js'; import * as utils from 'src/utils.js'; -import * as ajaxLib from 'src/ajax.js'; +import {server} from '../../mocks/xhr'; const pastDateString = new Date(0).toString() -function mockResponse(responseText, fakeResponse = (url, callback) => callback(responseText)) { - return function() { - return fakeResponse; - } -} - describe('CriteoId module', function () { const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000; @@ -22,7 +16,6 @@ describe('CriteoId module', function () { let removeFromLocalStorageStub; let timeStampStub; let parseUrlStub; - let ajaxBuilderStub; let triggerPixelStub; beforeEach(function (done) { @@ -32,7 +25,6 @@ describe('CriteoId module', function () { setLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); removeFromLocalStorageStub = sinon.stub(storage, 'removeDataFromLocalStorage'); timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); - ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockResponse('{}')); parseUrlStub = sinon.stub(utils, 'parseUrl').returns({protocol: 'https', hostname: 'testdev.com'}) triggerPixelStub = sinon.stub(utils, 'triggerPixel'); done(); @@ -45,7 +37,6 @@ describe('CriteoId module', function () { setLocalStorageStub.restore(); removeFromLocalStorageStub.restore(); timeStampStub.restore(); - ajaxBuilderStub.restore(); triggerPixelStub.restore(); parseUrlStub.restore(); }); @@ -61,8 +52,9 @@ describe('CriteoId module', function () { getCookieStub.withArgs('cto_bidid').returns(testCase.cookie); getLocalStorageStub.withArgs('cto_bidid').returns(testCase.localStorage); - const id = criteoIdSubmodule.getId(); - expect(id).to.be.deep.equal({id: testCase.expected ? { criteoId: testCase.expected } : undefined}); + const result = criteoIdSubmodule.getId(); + expect(result.id).to.be.deep.equal(testCase.expected ? { criteoId: testCase.expected } : undefined); + expect(result.callback).to.be.a('function'); })) it('decode() should return the bidId when it exists in local storages', function () { @@ -74,16 +66,21 @@ describe('CriteoId module', function () { getCookieStub.withArgs('cto_bundle').returns('bundle'); window.criteo_pubtag = {} - const emptyObj = '{}'; - let ajaxStub = sinon.stub().callsFake((url, callback) => callback(emptyObj)); - ajaxBuilderStub.callsFake(mockResponse(undefined, ajaxStub)) + let callBackSpy = sinon.spy(); + let result = criteoIdSubmodule.getId(); + result.callback(callBackSpy); - criteoIdSubmodule.getId(); const expectedUrl = `https://gum.criteo.com/sid/json?origin=prebid&topUrl=https%3A%2F%2Ftestdev.com%2F&domain=testdev.com&bundle=bundle&cw=1&pbt=1&lsw=1`; - expect(ajaxStub.calledWith(expectedUrl)).to.be.true; + let request = server.requests[0]; + expect(request.url).to.be.eq(expectedUrl); - window.criteo_pubtag = undefined; + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; }); const responses = [ @@ -101,31 +98,37 @@ describe('CriteoId module', function () { responses.forEach(response => describe('test user sync response behavior', function () { const expirationTs = new Date(nowTimestamp + cookiesMaxAge).toString(); - beforeEach(function (done) { - const fakeResponse = (url, callback) => { - callback(JSON.stringify(response)); - setTimeout(done, 0); - } - ajaxBuilderStub.callsFake(mockResponse(undefined, fakeResponse)); - criteoIdSubmodule.getId(); - }) - it('should save bidId if it exists', function () { + const result = criteoIdSubmodule.getId(); + result.callback((id) => { + expect(id).to.be.deep.equal(response.bidId ? { criteoId: response.bidId } : undefined); + }); + + let request = server.requests[0]; + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(response) + ); + if (response.acwsUrl) { expect(triggerPixelStub.called).to.be.true; expect(setCookieStub.calledWith('cto_bundle')).to.be.false; expect(setLocalStorageStub.calledWith('cto_bundle')).to.be.false; } else if (response.bundle) { - expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs)).to.be.true; + expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.testdev.com')).to.be.true; expect(setLocalStorageStub.calledWith('cto_bundle', response.bundle)).to.be.true; expect(triggerPixelStub.called).to.be.false; } if (response.bidId) { - expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs)).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.testdev.com')).to.be.true; expect(setLocalStorageStub.calledWith('cto_bidid', response.bidId)).to.be.true; } else { - expect(setCookieStub.calledWith('cto_bidid', '', pastDateString)).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', '', pastDateString, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', '', pastDateString, null, '.testdev.com')).to.be.true; expect(removeFromLocalStorageStub.calledWith('cto_bidid')).to.be.true; } }); @@ -140,16 +143,23 @@ describe('CriteoId module', function () { ]; gdprConsentTestCases.forEach(testCase => it('should call user sync url with the gdprConsent', function () { - const emptyObj = '{}'; - let ajaxStub = sinon.stub().callsFake((url, callback) => callback(emptyObj)); - ajaxBuilderStub.callsFake(mockResponse(undefined, ajaxStub)) - - criteoIdSubmodule.getId(undefined, testCase.consentData); + let callBackSpy = sinon.spy(); + let result = criteoIdSubmodule.getId(undefined, testCase.consentData); + result.callback(callBackSpy); + let request = server.requests[0]; if (testCase.expected) { - expect(ajaxStub.calledWithMatch(`gdprString=${testCase.expected}`)).to.be.true; + expect(request.url).to.have.string(`gdprString=${testCase.expected}`); } else { - expect(ajaxStub.calledWithMatch('gdprString')).not.to.be.true; + expect(request.url).to.not.have.string('gdprString'); } + + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({}) + ); + + expect(callBackSpy.calledOnce).to.be.true; })); }); diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index ccd205964a9..928c252943c 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -9,8 +9,11 @@ import { setConfig, addBidResponseHook, currencySupportEnabled, - currencyRates + currencyRates, + ready } from 'modules/currency.js'; +import {createBid} from '../../../src/bidfactory.js'; +import CONSTANTS from '../../../src/constants.json'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -22,8 +25,13 @@ describe('currency', function () { let fn = sinon.spy(); + function makeBid(bidProps) { + return Object.assign(createBid(CONSTANTS.STATUS.GOOD), bidProps); + } + beforeEach(function () { fakeCurrencyFileServer = sinon.fakeServer.create(); + ready.reset(); }); afterEach(function () { @@ -286,7 +294,7 @@ describe('currency', function () { }); describe('currency.addBidResponseDecorator bidResponseQueue', function () { - it('not run until currency rates file is loaded', function () { + it('not run until currency rates file is loaded', function (done) { setConfig({}); fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); @@ -296,21 +304,34 @@ describe('currency', function () { setConfig({ 'adServerCurrency': 'JPY' }); var marker = false; - addBidResponseHook(function() { + let promiseResolved = false; + addBidResponseHook(Object.assign(function() { marker = true; - }, 'elementId', bid); + }, { + bail: function (promise) { + promise.then(() => promiseResolved = true); + } + }), 'elementId', bid); expect(marker).to.equal(false); - fakeCurrencyFileServer.respond(); - expect(marker).to.equal(true); + setTimeout(() => { + expect(promiseResolved).to.be.false; + fakeCurrencyFileServer.respond(); + + setTimeout(() => { + expect(marker).to.equal(true); + expect(promiseResolved).to.be.true; + done(); + }); + }); }); }); describe('currency.addBidResponseDecorator', function () { it('should leave bid at 1 when currency support is not enabled and fromCurrency is USD', function () { setConfig({}); - var bid = { 'cpm': 1, 'currency': 'USD' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'USD' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -321,7 +342,7 @@ describe('currency', function () { it('should result in NO_BID when currency support is not enabled and fromCurrency is not USD', function () { setConfig({}); - var bid = { 'cpm': 1, 'currency': 'GBP' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'GBP' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -333,7 +354,7 @@ describe('currency', function () { setConfig({ 'adServerCurrency': 'USD' }); - var bid = { 'cpm': 1, 'currency': 'USD' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'USD' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -348,7 +369,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'JPY' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'ABC' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'ABC' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -360,7 +381,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'ABC' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'GBP' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'GBP' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -372,7 +393,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'JPY' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'JPY' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'JPY' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -385,7 +406,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'GBP' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'USD' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'USD' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -398,7 +419,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'GBP' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'CNY' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'CNY' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -411,7 +432,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'CNY' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'JPY' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'JPY' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; diff --git a/test/spec/modules/cwireBidAdapter_spec.js b/test/spec/modules/cwireBidAdapter_spec.js new file mode 100644 index 00000000000..3f922fc1471 --- /dev/null +++ b/test/spec/modules/cwireBidAdapter_spec.js @@ -0,0 +1,248 @@ +import { expect } from 'chai'; +import * as utils from '../../../src/utils.js'; +import { + spec, + CW_PAGE_VIEW_ID, + ENDPOINT_URL, + RENDERER_URL, +} from '../../../modules/cwireBidAdapter.js'; +import * as prebidGlobal from 'src/prebidGlobal.js'; + +// ------------------------------------ +// Bid Request Builder +// ------------------------------------ + +const BID_DEFAULTS = { + request: { + bidder: 'cwire', + auctionId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + transactionId: 'txaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + bidId: 'bid123445', + bidderRequestId: 'brid12345', + code: 'original-div', + }, + params: { + placementId: 123456, + pageId: 777, + adUnitElementId: 'target-div' + }, + sizes: [[300, 250], [1, 1]], +}; + +const BidderRequestBuilder = function BidderRequestBuilder(options) { + const defaults = { + bidderCode: 'cwire', + auctionId: BID_DEFAULTS.request.auctionId, + bidderRequestId: BID_DEFAULTS.request.bidderRequestId, + transactionId: BID_DEFAULTS.request.transactionId, + timeout: 3000, + }; + + const request = { + ...defaults, + ...options + }; + + this.build = () => request; +}; + +const BidRequestBuilder = function BidRequestBuilder(options, deleteKeys) { + const defaults = JSON.parse(JSON.stringify(BID_DEFAULTS)); + + const request = { + ...defaults.request, + ...options + }; + + if (request && utils.isArray(deleteKeys)) { + deleteKeys.forEach((k) => { + delete request[k]; + }) + } + + this.withParams = (options, deleteKeys) => { + request.params = { + ...defaults.params, + ...options + }; + if (request && utils.isArray(deleteKeys)) { + deleteKeys.forEach((k) => { + delete request.params[k]; + }) + } + return this; + }; + + this.build = () => request; +}; + +describe('C-WIRE bid adapter', () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + // START TESTING + describe('C-WIRE - isBidRequestValid', function () { + it('should return true when required params found', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + expect(spec.isBidRequestValid(bid01)).to.equal(true); + }); + + it('should fail if there is no placementId', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + delete bid01.params.placementId + expect(spec.isBidRequestValid(bid01)).to.equal(false); + }); + + it('should fail if invalid placementId type', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + delete bid01.params.placementId; + bid01.placementId = '322'; + expect(spec.isBidRequestValid(bid01)).to.equal(false); + }); + + it('should fail if there is no pageId', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + delete bid01.params.pageId + expect(spec.isBidRequestValid(bid01)).to.equal(false); + }); + + it('should fail if invalid pageId type', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + delete bid01.params.pageId; + bid01.params.pageId = '3320'; + expect(spec.isBidRequestValid(bid01)).to.equal(false); + }); + + it('should use params.adUnitElementId if provided', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + + expect(spec.isBidRequestValid(bid01)).to.equal(true); + expect(bid01.params.adUnitElementId).to.exist; + expect(bid01.params.adUnitElementId).to.equal('target-div'); + }); + + it('should use default adUnitCode if no adUnitElementId provided', function () { + const bid01 = new BidRequestBuilder().withParams({}, ['adUnitElementId']).build(); + expect(spec.isBidRequestValid(bid01)).to.equal(true); + expect(bid01.params.adUnitElementId).to.exist; + expect(bid01.params.adUnitElementId).to.equal('original-div'); + }); + }); + + describe('C-WIRE - buildRequests()', function () { + it('creates a valid request', function () { + // for whatever reason stub for getWindowLocation does not work + // so this was the closest way to test for get params + const params = sandbox.stub(utils, 'getParameterByName'); + params.withArgs('cwgroups').returns('group_1'); + params.withArgs('cwcreative').returns('54321'); + + const bid01 = new BidRequestBuilder({ + mediaTypes: { + banner: { + sizes: [[1, 1]], + } + } + }).withParams().build(); + + const bidderRequest01 = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest01); + + expect(requests.data.slots.length).to.equal(1); + expect(requests.data.cwid).to.be.null; + expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); + expect(requests.data.cwcreative).to.equal('54321'); + expect(requests.data.refgroups[0]).to.equal('group_1'); + }); + }); + + describe('C-WIRE - interpretResponse()', function () { + const serverResponse = { + body: { + bids: [{ + html: '

AD CONTENT

', + currency: 'CHF', + cpm: 43.37, + dimensions: [1, 1], + netRevenue: true, + creativeId: '1337', + requestId: BID_DEFAULTS.request.bidId, + ttl: 3500, + }], + } + }; + + const expectedResponse = [{ + ad: JSON.parse(JSON.stringify(serverResponse.body.bids[0].html)), + bidderCode: BID_DEFAULTS.request.bidder, + cpm: JSON.parse(JSON.stringify(serverResponse.body.bids[0].cpm)), + creativeId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].creativeId)), + currency: JSON.parse(JSON.stringify(serverResponse.body.bids[0].currency)), + height: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[0])), + width: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[1])), + netRevenue: JSON.parse(JSON.stringify(serverResponse.body.bids[0].netRevenue)), + requestId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].requestId)), + ttl: JSON.parse(JSON.stringify(serverResponse.body.bids[0].ttl)), + meta: { + advertiserDomains: [], + }, + mediaType: 'banner', + }] + + it('correctly parses response', function () { + const bid01 = new BidRequestBuilder({ + mediaTypes: { + banner: { + sizes: [[1, 1]], + } + } + }).withParams().build(); + + const bidderRequest01 = new BidderRequestBuilder().build(); + const requests = spec.buildRequests([bid01], bidderRequest01); + + const response = spec.interpretResponse(serverResponse, requests); + expect(response).to.deep.equal(expectedResponse); + }); + + it('attaches renderer', function () { + const bid01 = new BidRequestBuilder({ + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'outstream', + } + } + }).withParams().build(); + const bidderRequest01 = new BidderRequestBuilder().build(); + + const _serverResponse = utils.deepClone(serverResponse); + _serverResponse.body.bids[0].vastXml = ''; + + const _expectedResponse = utils.deepClone(expectedResponse); + _expectedResponse[0].mediaType = 'video'; + _expectedResponse[0].videoScript = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].html)); + _expectedResponse[0].vastXml = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].vastXml)); + delete _expectedResponse[0].ad; + + const requests = spec.buildRequests([bid01], bidderRequest01); + expect(requests.data.slots[0].sizes).to.deep.equal(['640x480']); + + const response = spec.interpretResponse(_serverResponse, requests); + expect(response[0].renderer).to.exist; + expect(response[0].renderer.url).to.equals(RENDERER_URL); + expect(response[0].renderer.loaded).to.equals(false); + + delete response[0].renderer; + expect(response).to.deep.equal(_expectedResponse); + }); + }); +}); diff --git a/test/spec/modules/dailyhuntBidAdapter_spec.js b/test/spec/modules/dailyhuntBidAdapter_spec.js index d571150dbee..f347d6cec5b 100644 --- a/test/spec/modules/dailyhuntBidAdapter_spec.js +++ b/test/spec/modules/dailyhuntBidAdapter_spec.js @@ -283,7 +283,8 @@ describe('DailyhuntAdapter', function () { currency: 'USD', ad: 'adm', mediaType: 'banner', - winUrl: 'winUrl' + winUrl: 'winUrl', + adomain: 'dailyhunt' }, { requestId: '2', @@ -296,6 +297,7 @@ describe('DailyhuntAdapter', function () { currency: 'USD', mediaType: 'video', winUrl: 'winUrl', + adomain: 'dailyhunt', videoCacheKey: 'cache_key', vastUrl: 'vastUrl', }, @@ -310,6 +312,7 @@ describe('DailyhuntAdapter', function () { currency: 'USD', mediaType: 'native', winUrl: 'winUrl', + adomain: 'dailyhunt', native: { clickUrl: 'https%3A%2F%2Fmontu1996.github.io%2F', clickTrackers: [], @@ -336,6 +339,7 @@ describe('DailyhuntAdapter', function () { currency: 'USD', mediaType: 'video', winUrl: 'winUrl', + adomain: 'dailyhunt', vastXml: 'adm', }, ]; diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index 18b8aac7371..ff7b0aad48c 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -1,12 +1,15 @@ import { expect } from 'chai'; import { spec } from '../../../modules/datablocksBidAdapter.js'; +import { BotClientTests } from '../../../modules/datablocksBidAdapter.js'; +import { getStorageManager } from '../../../src/storageManager.js'; +export let storage = getStorageManager(); -let bid = { +const bid = { bidId: '2dd581a2b6281d', bidder: 'datablocks', bidderRequestId: '145e1d6a7837c9', params: { - sourceId: 7560, + source_id: 7560, host: 'v5demo.datablocks.net' }, adUnitCode: '/19968336/header-bid-tag-0', @@ -24,12 +27,12 @@ let bid = { transactionId: '1ccbee15-f6f6-46ce-8998-58fe5542e8e1' }; -let bid2 = { +const bid2 = { bidId: '2dd581a2b624324g', bidder: 'datablocks', bidderRequestId: '145e1d6a7837543', params: { - sourceId: 7560, + source_id: 7560, host: 'v5demo.datablocks.net' }, adUnitCode: '/19968336/header-bid-tag-0', @@ -43,7 +46,7 @@ let bid2 = { transactionId: '1ccbee15-f6f6-46ce-8998-58fe55425432' }; -let nativeBid = { +const nativeBid = { adUnitCode: '/19968336/header-bid-tag-0', auctionId: '160c78a4-f808-410f-b682-d8728f3a79ee', bidId: '332045ee374a99', @@ -78,253 +81,445 @@ let nativeBid = { } }, params: { - sourceId: 7560, + source_id: 7560, host: 'v5demo.datablocks.net' }, transactionId: '0a4e9788-4def-4b94-bc25-564d7cac99f6' } -let videoBid = { - adUnitCode: '/19968336/header-bid-tag-0', - auctionId: '160c78a4-f808-410f-b682-d8728f3a79e1', - bidId: '332045ee374b99', - bidder: 'datablocks', - bidderRequestId: '15d9012765e36d', - mediaTypes: { - video: { - context: 'instream', - playerSize: [501, 400], - durationRangeSec: [15, 60] - } - }, - params: { - sourceId: 7560, - host: 'v5demo.datablocks.net', - video: { - minduration: 14 - } - }, - transactionId: '0a4e9788-4def-4b94-bc25-564d7cac99f7' -} - const bidderRequest = { auctionId: '8bfef1be-d3ac-4d18-8859-754c7b4cf017', auctionStart: Date.now(), biddeCode: 'datablocks', bidderRequestId: '10c47a5fc3c41', - bids: [bid, bid2, nativeBid, videoBid], + bids: [bid, bid2, nativeBid], refererInfo: { numIframes: 0, reachedTop: true, - referer: 'https://v5demo.datablocks.net/test', - stack: ['https://v5demo.datablocks.net/test'] + referer: 'https://7560.v5demo.datablocks.net/test', + stack: ['https://7560.v5demo.datablocks.net/test'] }, start: Date.now(), timeout: 10000 }; -let resObject = { +const res_object = { body: { - id: '10c47a5fc3c41', - bidid: '166895245-28-11347-1', - seatbid: [{ - seat: '7560', - bid: [{ - id: '1090738570', - impid: '2966b257c81d27', - price: 24.000000, - adm: 'RON', - cid: '55', - adid: '177654', - crid: '177656', - cat: [], - api: [], - w: 300, - h: 250 - }, { - id: '1090738571', - impid: '2966b257c81d28', - price: 24.000000, - adm: 'RON', - cid: '55', - adid: '177654', - crid: '177656', - cat: [], - api: [], - w: 728, - h: 90 - }, { - id: '1090738570', - impid: '15d9012765e36c', - price: 24.000000, - adm: '{"native":{"ver":"1.2","assets":[{"id":1,"required":1,"title":{"text":"Example Title"}},{"id":2,"required":1,"data":{"value":"Example Body"}},{"id":3,"required":1,"img":{"url":"https://example.image.com/"}}],"link":{"url":"https://click.example.com/c/264597/?fcid=29699699045816"},"imptrackers":["https://impression.example.com/i/264597/?fcid=29699699045816"]}}', - cid: '132145', - adid: '154321', - crid: '177432', - cat: [], - api: [] - }, { - id: '1090738575', - impid: '15d9012765e36f', - price: 25.000000, - cid: '12345', - adid: '12345', - crid: '123456', - nurl: 'https://click.v5demo.datablocks.net/m//?fcid=435235435432', - cat: [], - api: [], - w: 500, - h: 400 - }] - }], - cur: 'USD', - ext: {} + 'id': '10c47a5fc3c41', + 'bidid': '217868445-30021-19053-0', + 'seatbid': [ + { + 'id': '22621593137287', + 'impid': '1', + 'adm': 'John is great', + 'adomain': ['medianet.com'], + 'price': 0.430000, + 'cid': '2524568', + 'adid': '0', + 'crid': '0', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'type': 'CPM', + 'mtype': 'banner' + } + }, + { + 'id': '22645215457415', + 'impid': '2', + 'adm': 'john is the best', + 'adomain': ['td.com'], + 'price': 0.580000, + 'cid': '2524574', + 'adid': '0', + 'crid': '0', + 'cat': [], + 'w': 728, + 'h': 90, + 'ext': { + 'type': 'CPM', + 'mtype': 'banner' + } + }, + + { + 'id': '22645215457416', + 'impid': '3', + 'adm': '{"native":{"ver":"1.2","assets":[{"id":1,"required":1,"title":{"text":"John is amazing"}},{"id":5,"required":1,"data":{"value":"Sponsored by John"}},{"id":3,"required":1,"img":{"url":"https://example.image.com/", "h":"360", "w":"360"}}],"link":{"url":"https://click.example.com/c/264597/?fcid=29699699045816"},"imptrackers":["https://impression.example.com/i/264597/?fcid=29699699045816"]}}', + 'adomain': ['td.com'], + 'price': 10.00, + 'cid': '2524574', + 'adid': '0', + 'crid': '0', + 'cat': [], + 'ext': { + 'type': 'CPM', + 'mtype': 'native' + } + } + ], + 'cur': 'USD', + 'ext': { + 'version': '1.2.93', + 'buyerid': '1234567', + 'syncs': [ + { + 'type': 'iframe', + 'url': 'https://s.0cf.io' + }, + { + 'type': 'image', + 'url': 'https://us.dblks.net/set_uid/' + } + ] + } } -}; -let bidRequest = { +} + +let bid_request = { method: 'POST', - url: 'https://v5demo.datablocks.net/search/?sid=7560', + url: 'https://prebid.datablocks.net/openrtb/?sid=2523014', options: { - withCredentials: false + withCredentials: true }, data: { - device: { - ip: 'peer', - ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) Ap…ML, like Gecko) Chrome/73.0.3683.86 Safari/537.36', - js: 1, - language: 'en' - }, - id: '10c47a5fc3c41', - imp: [{ - banner: { w: 300, h: 250 }, - id: '2966b257c81d27', - secure: false, - tagid: '/19968336/header-bid-tag-0' - }, { - banner: { w: 728, h: 90 }, - id: '2966b257c81d28', - secure: false, - tagid: '/19968336/header-bid-tag-0' - }, { - id: '15d9012765e36c', - native: {request: '{"native":{"assets":[{"id":"1","required":true,"title":{"len":140}},{"id":"2","required":true,"data":{"type":2}},{"id":"3","img":{"w":728,"h":90,"type":3}}]}}'}, - secure: false, - tagid: '/19968336/header-bid-tag-0' - }, { - id: '15d9012765e36f', - video: {w: 500, h: 400, minduration: 15, maxduration: 60}, - secure: false, - tagid: '/19968336/header-bid-tag-0' - }], - site: { - domain: '', - id: 'blank', - page: 'https://v5demo.datablocks.net/test' - } + 'id': 'c09c6e47-8bdb-4884-a46d-93165322b368', + 'imp': [{ + 'id': '1', + 'tagid': '/19968336/header-bid-tag-0', + 'placement_id': 0, + 'secure': true, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{ + 'w': 300, + 'h': 250 + }, { + 'w': 300, + 'h': 600 + }] + } + }, { + 'id': '2', + 'tagid': '/19968336/header-bid-tag-1', + 'placement_id': 12345, + 'secure': true, + 'banner': { + 'w': 729, + 'h': 90, + 'format': [{ + 'w': 729, + 'h': 90 + }, { + 'w': 970, + 'h': 250 + }] + } + }, { + 'id': '3', + 'tagid': '/19968336/prebid_multiformat_test', + 'placement_id': 0, + 'secure': true, + 'native': { + 'ver': '1.2', + 'request': { + 'assets': [{ + 'required': 1, + 'id': 1, + 'title': {} + }, { + 'required': 1, + 'id': 3, + 'img': { + 'type': 3 + } + }, { + 'required': 1, + 'id': 5, + 'data': { + 'type': 1 + } + }], + 'context': 1, + 'plcmttype': 1, + 'ver': '1.2' + } + } + }], + 'site': { + 'domain': 'test.datablocks.net', + 'page': 'https://test.datablocks.net/index.html', + 'schain': {}, + 'ext': { + 'p_domain': 'https://test.datablocks.net', + 'rt': true, + 'frames': 0, + 'stack': ['https://test.datablocks.net/index.html'], + 'timeout': 3000 + }, + 'keywords': 'HTML, CSS, JavaScript' + }, + 'device': { + 'ip': 'peer', + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36', + 'js': 1, + 'language': 'en', + 'buyerid': '1234567', + 'ext': { + 'pb_eids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'test', + 'atype': 1 + }] + }], + 'syncs': { + '1000': 'db_4044853', + '1001': true + }, + 'coppa': 0, + 'gdpr': {}, + 'usp': {}, + 'client_info': { + 'wiw': 2560, + 'wih': 1281, + 'saw': 2560, + 'sah': 1417, + 'scd': 24, + 'sw': 2560, + 'sh': 1440, + 'whl': 4, + 'wxo': 0, + 'wyo': 0, + 'wpr': 2, + 'is_bot': false, + 'is_hid': false, + 'vs': 'hidden' + }, + 'fpd': {} + } + } } } describe('DatablocksAdapter', function() { + describe('All needed functions are available', function() { + it(`isBidRequestValid is present and type function`, function () { + expect(spec.isBidRequestValid).to.exist.and.to.be.a('function') + }); + + it(`buildRequests is present and type function`, function () { + expect(spec.buildRequests).to.exist.and.to.be.a('function') + }); + + it(`getUserSyncs is present and type function`, function () { + expect(spec.getUserSyncs).to.exist.and.to.be.a('function') + }); + + it(`onBidWon is present and type function`, function () { + expect(spec.onBidWon).to.exist.and.to.be.a('function') + }); + + it(`onSetTargeting is present and type function`, function () { + expect(spec.onSetTargeting).to.exist.and.to.be.a('function') + }); + + it(`interpretResponse is present and type function`, function () { + expect(spec.interpretResponse).to.exist.and.to.be.a('function') + }); + + it(`store_dbid is present and type function`, function () { + expect(spec.store_dbid).to.exist.and.to.be.a('function') + }); + + it(`get_dbid is present and type function`, function () { + expect(spec.get_dbid).to.exist.and.to.be.a('function') + }); + + it(`store_syncs is present and type function`, function () { + expect(spec.store_syncs).to.exist.and.to.be.a('function') + }); + + it(`get_syncs is present and type function`, function () { + expect(spec.get_syncs).to.exist.and.to.be.a('function') + }); + + it(`queue_metric is present and type function`, function () { + expect(spec.queue_metric).to.exist.and.to.be.a('function') + }); + + it(`send_metrics is present and type function`, function () { + expect(spec.send_metrics).to.exist.and.to.be.a('function') + }); + + it(`get_client_info is present and type function`, function () { + expect(spec.get_client_info).to.exist.and.to.be.a('function') + }); + + it(`get_viewability is present and type function`, function () { + expect(spec.get_viewability).to.exist.and.to.be.a('function') + }); + }); + + describe('get / store dbid', function() { + it('Should return true / undefined', function() { + expect(spec.store_dbid('12345')).to.be.true; + expect(spec.get_dbid()).to.be.a('string'); + }); + }) + + describe('get / store syncs', function() { + it('Should return true / array', function() { + expect(spec.store_syncs([{id: 1, uid: 'test'}])).to.be.true; + expect(spec.get_syncs()).to.be.a('object'); + }); + }) + + describe('queue / send metrics', function() { + it('Should return true', function() { + expect(spec.queue_metric({type: 'test'})).to.be.true; + expect(spec.queue_metric('string')).to.be.false; + expect(spec.send_metrics()).to.be.true; + }); + }) + + describe('get_viewability', function() { + it('Should return undefined', function() { + expect(spec.get_viewability()).to.equal(undefined); + }); + }) + + describe('get client info', function() { + it('Should return object', function() { + let client_info = spec.get_client_info() + expect(client_info).to.be.a('object'); + expect(client_info).to.have.all.keys('wiw', 'wih', 'saw', 'sah', 'scd', 'sw', 'sh', 'whl', 'wxo', 'wyo', 'wpr', 'is_bot', 'is_hid', 'vs'); + }); + + it('bot test should return boolean', function() { + let bot_test = new BotClientTests(); + expect(bot_test.doTests()).to.be.a('boolean'); + }); + }) + describe('isBidRequestValid', function() { - it('Should return true when sourceId and Host are set', function() { + it('Should return true when source_id and Host are set', function() { expect(spec.isBidRequestValid(bid)).to.be.true; }); - it('Should return false when host/sourceId is not set', function() { + it('Should return false when host/source_id is not set', function() { let moddedBid = Object.assign({}, bid); - delete moddedBid.params.sourceId; - delete moddedBid.params.host; - expect(spec.isBidRequestValid(bid)).to.be.false; + delete moddedBid.params.source_id; + expect(spec.isBidRequestValid(moddedBid)).to.be.false; + }); + + it('Should return true when viewability reporting is opted out', function() { + let moddedBid = Object.assign({}, bid); + moddedBid.params.vis_optout = true; + spec.isBidRequestValid(moddedBid); + expect(spec.db_obj.vis_optout).to.be.true; + }); + }) + + describe('getUserSyncs', function() { + it('Should return array of syncs', function() { + expect(spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [res_object], {gdprApplies: true, gdpr: 1, gdpr_consent: 'consent_string'}, {})).to.be.an('array'); + }); + }); + + describe('onSetTargeting', function() { + it('Should return undefined', function() { + expect(spec.onSetTargeting()).to.equal(undefined); + }); + }); + + describe('onBidWon', function() { + it('Should return undefined', function() { + let won_bid = {params: [{source_id: 1}], requestId: 1, adUnitCode: 'unit', auctionId: 1, size: '300x250', cpm: 10, adserverTargeting: {hb_pb: 10}, timeToRespond: 10, ttl: 10}; + expect(spec.onBidWon(won_bid)).to.equal(undefined); }); }); describe('buildRequests', function() { - let requests = spec.buildRequests([bid, bid2, nativeBid, videoBid], bidderRequest); + let request = spec.buildRequests([bid, bid2, nativeBid], bidderRequest); + + expect(request).to.exist; + it('Returns POST method', function() { + expect(request.method).to.exist; + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function() { + expect(request.url).to.exist; + expect(request.url).to.equal('https://v5demo.datablocks.net/openrtb/?sid=7560'); + }); + it('Creates an array of request objects', function() { - expect(requests).to.be.an('array').that.is.not.empty; + expect(request.data.imp).to.be.an('array').that.is.not.empty; }); - requests.forEach(request => { - expect(request).to.exist; - it('Returns POST method', function() { - expect(request.method).to.exist; - expect(request.method).to.equal('POST'); - }); - it('Returns valid URL', function() { - expect(request.url).to.exist; - expect(request.url).to.equal('https://v5demo.datablocks.net/search/?sid=7560'); - }); + it('Should be a valid openRTB request', function() { + let data = request.data; - it('Should be a valid openRTB request', function() { - let data = request.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys('device', 'imp', 'site', 'id'); - expect(data.id).to.be.a('string'); - - let imps = data['imp']; - imps.forEach((imp, index) => { - let curBid = bidderRequest.bids[index]; - if (imp.banner) { - expect(imp).to.have.all.keys('banner', 'id', 'secure', 'tagid'); - expect(imp.banner).to.be.a('object'); - } else if (imp.native) { - expect(imp).to.have.all.keys('native', 'id', 'secure', 'tagid'); - expect(imp.native).to.have.all.keys('request'); - expect(imp.native.request).to.be.a('string'); - let native = JSON.parse(imp.native.request); - expect(native).to.be.a('object'); - } else if (imp.video) { - expect(imp).to.have.all.keys('video', 'id', 'secure', 'tagid'); - expect(imp.video).to.have.all.keys('w', 'h', 'minduration', 'maxduration') - } else { - expect(true).to.equal(false); - } - - expect(imp.id).to.be.a('string'); - expect(imp.id).to.equal(curBid.bidId); - expect(imp.tagid).to.be.a('string'); - expect(imp.tagid).to.equal(curBid.adUnitCode); - expect(imp.secure).to.equal(false); - }) - - expect(data.device.ip).to.equal('peer'); - }); - }) + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('device', 'imp', 'site', 'id'); + expect(data.id).to.be.a('string'); + expect(data.imp).to.be.a('array'); + expect(data.device.ip).to.equal('peer'); + + let imps = data['imp']; + imps.forEach((imp, index) => { + let curBid = bidderRequest.bids[index]; + if (imp.banner) { + expect(imp.banner).to.be.a('object'); + expect(imp).to.have.all.keys('banner', 'id', 'secure', 'tagid', 'placement_id', 'ortb2', 'floor'); + } else if (imp.native) { + expect(imp).to.have.all.keys('native', 'id', 'secure', 'tagid', 'placement_id', 'ortb2', 'floor'); + expect(imp.native).to.have.all.keys('request', 'ver'); + expect(imp.native.request).to.be.a('object'); + } else { + expect(true).to.equal(false); + } + + expect(imp.id).to.be.a('string'); + expect(imp.id).to.equal(curBid.bidId); + expect(imp.tagid).to.be.a('string'); + expect(imp.tagid).to.equal(curBid.adUnitCode); + expect(imp.secure).to.equal(false); + }) + }); it('Returns empty data if no valid requests are passed', function() { - let request = spec.buildRequests([]); - expect(request).to.be.an('array').that.is.empty; + let test_request = spec.buildRequests([]); + expect(test_request).to.be.an('array').that.is.empty; }); }); + describe('interpretResponse', function() { - let serverResponses = spec.interpretResponse(resObject, bidRequest); + let response = spec.interpretResponse(res_object, bid_request); + it('Returns an array of valid server responses if response object is valid', function() { - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - let dataItem = serverResponses[i]; - expect(Object.keys(dataItem)).to.include('cpm', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType', 'requestId'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.mediaType).to.be.a('string'); - - if (dataItem.mediaType == 'banner') { - expect(dataItem.ad).to.be.a('string'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - } else if (dataItem.mediaType == 'native') { - expect(dataItem.native.title).to.be.a('string'); - expect(dataItem.native.body).to.be.a('string'); - expect(dataItem.native.clickUrl).to.be.a('string'); - } else if (dataItem.mediaType == 'video') { - expect(dataItem.vastUrl).to.be.a('string'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); + expect(response).to.be.an('array').that.is.not.empty; + + response.forEach(bid => { + expect(parseInt(bid.requestId)).to.be.a('number').greaterThan(0); + expect(bid.cpm).to.be.a('number'); + expect(bid.creativeId).to.be.a('string'); + expect(bid.currency).to.be.a('string'); + expect(bid.netRevenue).to.be.a('boolean'); + expect(bid.ttl).to.be.a('number'); + expect(bid.mediaType).to.be.a('string'); + + if (bid.mediaType == 'banner') { + expect(bid.width).to.be.a('number'); + expect(bid.height).to.be.a('number'); + expect(bid.ad).to.be.a('string'); + } else if (bid.mediaType == 'native') { + expect(bid.native).to.be.a('object'); } - } + }) + it('Returns an empty array if invalid response is passed', function() { serverResponses = spec.interpretResponse('invalid_response'); expect(serverResponses).to.be.an('array').that.is.empty; diff --git a/test/spec/modules/dchain_spec.js b/test/spec/modules/dchain_spec.js new file mode 100644 index 00000000000..45061c539c1 --- /dev/null +++ b/test/spec/modules/dchain_spec.js @@ -0,0 +1,329 @@ +import { checkDchainSyntax, addBidResponseHook } from '../../../modules/dchain.js'; +import { config } from '../../../src/config.js'; +import { expect } from 'chai'; + +describe('dchain module', function () { + const STRICT = 'strict'; + const RELAX = 'relaxed'; + const OFF = 'off'; + + describe('checkDchainSyntax', function () { + let bid; + + beforeEach(function () { + bid = { + meta: { + dchain: { + 'ver': '1.0', + 'complete': 0, + 'ext': {}, + 'nodes': [{ + 'asi': 'domain.com', + 'bsid': '12345', + }, { + 'name': 'bidder', + 'domain': 'bidder.com', + 'ext': {} + }] + } + } + }; + }); + + it('Returns false if complete param is not 0 or 1', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.complete = 0; // integer + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.complete = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.complete = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.complete; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if ver param is not a String', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.ver = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.ver = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.ver; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if ext param is not an Object', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.ext = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.true; + delete dchainConfig.ext; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.ext = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes param is not an Array', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if unknown field is used in main dchain', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.test = '1'; // String + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].asi is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].asi = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].asi = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].asi = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].asi; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].asi = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].asi = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].bsid is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].bsid = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].bsid = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].bsid = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].bsid; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].bsid = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].bsid = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].rid is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].rid = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].rid = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].rid = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].rid; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].rid = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].rid = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].name is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].name = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].name = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].name = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].name; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].name = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].name = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].domain is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].domain = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].domain = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].domain = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].domain; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].domain = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].domain = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].ext is not an Object', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.nodes[0].ext = '1'; // String + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.true; + delete dchainConfig.nodes[0].ext; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].ext = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if unknown field is used in nodes[]', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.nodes[0].test = '1'; // String + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Relaxed mode: returns true even for invalid config', function () { + bid.meta.dchain = { + ver: 1.1, + complete: '0', + nodes: [{ + name: 'asdf', + domain: ['domain.com'] + }] + }; + + expect(checkDchainSyntax(bid, RELAX)).to.true; + }); + }); + + describe('addBidResponseHook', function () { + let bid; + let adUnitCode = 'adUnit1'; + + beforeEach(function () { + bid = { + bidderCode: 'bidderA', + meta: { + dchain: { + 'ver': '1.0', + 'complete': 0, + 'ext': {}, + 'nodes': [{ + 'asi': 'domain.com', + 'bsid': '12345', + }, { + 'name': 'bidder', + 'domain': 'bidder.com', + 'ext': {} + }] + }, + networkName: 'myNetworkName', + networkId: 8475 + } + }; + }); + + afterEach(function () { + config.resetConfig(); + }); + + it('good strict config should append a node object to existing bid.meta.dchain object', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.nodes[1]).to.exist.and.to.deep.equal({ + 'name': 'bidder', + 'domain': 'bidder.com', + 'ext': {} + }); + expect(bid.meta.dchain.nodes[2]).to.exist.and.to.deep.equal({ asi: 'bidderA' }); + } + + config.setConfig({ dchain: { validation: STRICT } }); + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('bad strict config should delete the bid.meta.dchain object', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.not.exist; + } + + config.setConfig({ dchain: { validation: STRICT } }); + bid.meta.dchain.complete = 3; + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('relaxed config should allow bid.meta.dchain to proceed, even with bad values', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.complete).to.exist.and.to.equal(3); + expect(bid.meta.dchain.nodes[2]).to.exist.and.to.deep.equal({ asi: 'bidderA' }); + } + + config.setConfig({ dchain: { validation: RELAX } }); + bid.meta.dchain.complete = 3; + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('off config should allow the bid.meta.dchain to proceed', function () { + // check for missing nodes + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.complete).to.exist.and.to.equal(0); + expect(bid.meta.dchain.nodes).to.exist.and.to.deep.equal({ test: 123 }); + } + + config.setConfig({ dchain: { validation: OFF } }); + bid.meta.dchain.nodes = { test: 123 }; + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('no bidder dchain', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.ver).to.exist.and.to.equal('1.0'); + expect(bid.meta.dchain.complete).to.exist.and.to.equal(0); + expect(bid.meta.dchain.nodes).to.exist.and.to.deep.equal([{ name: 'myNetworkName', bsid: '8475' }, { name: 'bidderA' }]); + } + + delete bid.meta.dchain; + config.setConfig({ dchain: { validation: RELAX } }); + addBidResponseHook(testCallback, adUnitCode, bid); + }); + }); +}); diff --git a/test/spec/modules/debugging_mod_spec.js b/test/spec/modules/debugging_mod_spec.js new file mode 100644 index 00000000000..79866d023e9 --- /dev/null +++ b/test/spec/modules/debugging_mod_spec.js @@ -0,0 +1,451 @@ +import {expect} from 'chai'; +import {BidInterceptor} from '../../../modules/debugging/bidInterceptor.js'; +import {bidderBidInterceptor} from '../../../modules/debugging/index.js'; +import {pbsBidInterceptor} from '../../../modules/debugging/pbsInterceptor.js'; + +describe('bid interceptor', () => { + let interceptor, mockSetTimeout; + beforeEach(() => { + mockSetTimeout = sinon.stub().callsFake((fn) => fn()); + interceptor = new BidInterceptor({setTimeout: mockSetTimeout}); + }); + + function setRules(...rules) { + interceptor.updateConfig({ + intercept: rules + }); + } + + describe('serializeConfig', () => { + Object.entries({ + regexes: /pat/, + functions: () => ({}) + }).forEach(([test, arg]) => { + it(`should filter out ${test}`, () => { + const valid = [{key1: 'value'}, {key2: 'value'}]; + const ser = interceptor.serializeConfig([...valid, {outer: {inner: arg}}]); + expect(ser).to.eql(valid); + }); + }); + }); + + describe('match()', () => { + Object.entries({ + value: {key: 'value'}, + regex: {key: /^value$/}, + 'function': (o) => o.key === 'value' + }).forEach(([test, matcher]) => { + describe(`by ${test}`, () => { + it('should work on matching top-level properties', () => { + setRules({when: matcher}); + const rule = interceptor.match({key: 'value'}); + expect(rule).to.not.eql(null); + }); + + it('should work on matching nested properties', () => { + setRules({when: {outer: {inner: matcher}}}); + const rule = interceptor.match({outer: {inner: {key: 'value'}}}); + expect(rule).to.not.eql(null); + }); + + it('should not work on non-matching inputs', () => { + setRules({when: matcher}); + expect(interceptor.match({key: 'different-value'})).to.not.be.ok; + expect(interceptor.match({differentKey: 'value'})).to.not.be.ok; + }); + }); + }); + + it('should respect rule order', () => { + setRules({when: {key: 'value'}}, {when: {}}, {when: {}}); + const rule = interceptor.match({}); + expect(rule.no).to.equal(2); + }); + + it('should pass extra arguments to property function matchers', () => { + let matchDef = { + key: sinon.stub(), + outer: {inner: {key: sinon.stub()}} + }; + const extraArgs = [{}, {}]; + setRules({when: matchDef}); + interceptor.match({key: {}, outer: {inner: {key: {}}}}, ...extraArgs); + [matchDef.key, matchDef.outer.inner.key].forEach((fn) => { + expect(fn.calledOnceWith(sinon.match.any, ...extraArgs.map(sinon.match.same))).to.be.true; + }); + }); + + it('should pass extra arguments to single-function matcher', () => { + let matchDef = sinon.stub(); + setRules({when: matchDef}); + const args = [{}, {}, {}]; + interceptor.match(...args); + expect(matchDef.calledOnceWith(...args.map(sinon.match.same))).to.be.true; + }); + }); + + describe('rule', () => { + function matchingRule({replace, options}) { + setRules({when: {}, then: replace, options: options}); + return interceptor.match({}); + } + + describe('.replace()', () => { + const REQUIRED_KEYS = [ + // https://docs.prebid.org/dev-docs/bidder-adaptor.html#bidder-adaptor-Interpreting-the-Response + 'requestId', 'cpm', 'currency', 'width', 'height', 'ttl', + 'creativeId', 'netRevenue', 'meta', 'ad' + ]; + it('should include required bid response keys by default', () => { + expect(matchingRule({}).replace({})).to.include.keys(REQUIRED_KEYS); + }); + + Object.entries({ + value: {key: 'value'}, + 'function': () => ({key: 'value'}) + }).forEach(([test, replDef]) => { + describe(`by ${test}`, () => { + it('should merge top-level properties with replace definition', () => { + const result = matchingRule({replace: replDef}).replace({}); + expect(result).to.include.keys(REQUIRED_KEYS); + expect(result.key).to.equal('value'); + }); + + it('should merge nested properties with replace definition', () => { + const result = matchingRule({replace: {outer: {inner: replDef}}}).replace({}); + expect(result).to.include.keys(REQUIRED_KEYS); + expect(result.outer.inner).to.eql({key: 'value'}); + }); + }); + }); + + it('should pass extra arguments to single function replacer', () => { + const replDef = sinon.stub(); + const args = [{}, {}, {}]; + matchingRule({replace: replDef}).replace(...args); + expect(replDef.calledOnceWith(...args.map(sinon.match.same))).to.be.true; + }); + + it('should pass extra arguments to function property replacers', () => { + const replDef = { + key: sinon.stub(), + outer: {inner: {key: sinon.stub()}} + }; + const args = [{}, {}, {}]; + matchingRule({replace: replDef}).replace(...args); + [replDef.key, replDef.outer.inner.key].forEach((repl) => { + expect(repl.calledOnceWith(...args.map(sinon.match.same))).to.be.true; + }); + }); + }); + + describe('.options', () => { + it('should include default rule options', () => { + const optDef = {someOption: 'value'}; + const ruleOptions = matchingRule({options: optDef}).options; + expect(ruleOptions).to.include(optDef); + expect(ruleOptions).to.include(interceptor.DEFAULT_RULE_OPTIONS); + }); + + it('should override defaults', () => { + const optDef = {delay: 123}; + const ruleOptions = matchingRule({options: optDef}).options; + expect(ruleOptions).to.eql(optDef); + }); + }); + }); + + describe('intercept()', () => { + let done, addBid; + + function intercept(args = {}) { + const bidRequest = {bids: args.bids || []}; + return interceptor.intercept(Object.assign({bidRequest, done, addBid}, args)); + } + + beforeEach(() => { + done = sinon.spy(); + addBid = sinon.spy(); + }); + + describe('on no match', () => { + it('should return untouched bids and bidRequest', () => { + const bids = [{}, {}]; + const bidRequest = {}; + const result = intercept({bids, bidRequest}); + expect(result.bids).to.equal(bids); + expect(result.bidRequest).to.equal(bidRequest); + }); + + it('should call done() immediately', () => { + intercept(); + expect(done.calledOnce).to.be.true; + expect(mockSetTimeout.args[0][1]).to.equal(0); + }); + + it('should not call addBid', () => { + intercept(); + expect(addBid.called).to.not.be.ok; + }); + }); + + describe('on match', () => { + let match1, match2, repl1, repl2; + const DELAY_1 = 123; + const DELAY_2 = 321; + const REQUEST = { + bids: [ + {id: 1, match: false}, + {id: 2, match: 1}, + {id: 3, match: 2} + ] + }; + + beforeEach(() => { + match1 = sinon.stub().callsFake((bid) => bid.match === 1); + match2 = sinon.stub().callsFake((bid) => bid.match === 2); + repl1 = sinon.stub().returns({replace: 1}); + repl2 = sinon.stub().returns({replace: 2}); + setRules( + {when: match1, then: repl1, options: {delay: DELAY_1}}, + {when: match2, then: repl2, options: {delay: DELAY_2}}, + ); + }); + + it('should return only non-matching bids', () => { + const {bids, bidRequest} = intercept({bidRequest: REQUEST}); + expect(bids).to.eql([REQUEST.bids[0]]); + expect(bidRequest.bids).to.eql([REQUEST.bids[0]]); + }); + + it('should call addBid for each matching bid', () => { + intercept({bidRequest: REQUEST}); + expect(addBid.callCount).to.equal(2); + expect(addBid.calledWith(sinon.match({replace: 1, isDebug: true}), REQUEST.bids[1])).to.be.true; + expect(addBid.calledWith(sinon.match({replace: 2, isDebug: true}), REQUEST.bids[2])).to.be.true; + [DELAY_1, DELAY_2].forEach((delay) => { + expect(mockSetTimeout.calledWith(sinon.match.any, delay)).to.be.true; + }); + }); + + it('should call done()', () => { + intercept({bidRequest: REQUEST}); + expect(done.calledOnce).to.be.true; + }); + + it('should pass bid and bidRequest to match and replace functions', () => { + intercept({bidRequest: REQUEST}); + Object.entries({ + 1: [match1, repl1], + 2: [match2, repl2] + }).forEach(([index, fns]) => { + fns.forEach((fn) => { + expect(fn.calledWith(REQUEST.bids[index], REQUEST)).to.be.true; + }); + }); + }); + }); + }); +}); + +describe('bidderBidInterceptor', () => { + let next, interceptBids, onCompletion, interceptResult, done, addBid; + + function interceptorArgs({spec = {}, bids = [], bidRequest = {}, ajax = {}, wrapCallback = {}, cbs = {}} = {}) { + return [next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, Object.assign({onCompletion}, cbs)]; + } + + beforeEach(() => { + next = sinon.spy(); + interceptBids = sinon.stub().callsFake((opts) => { + done = opts.done; + addBid = opts.addBid; + return interceptResult; + }); + onCompletion = sinon.spy(); + interceptResult = {bids: [], bidRequest: {}}; + }); + + it('should pass to interceptBid an addBid that triggers onBid', () => { + const onBid = sinon.spy(); + bidderBidInterceptor(...interceptorArgs({cbs: {onBid}})); + const bid = {}; + addBid(bid); + expect(onBid.calledWith(sinon.match.same(bid))).to.be.true; + }); + + describe('with no remaining bids', () => { + it('should pass a done callback that triggers onCompletion', () => { + bidderBidInterceptor(...interceptorArgs()); + expect(onCompletion.calledOnce).to.be.false; + interceptBids.args[0][0].done(); + expect(onCompletion.calledOnce).to.be.true; + }); + + it('should not call next()', () => { + bidderBidInterceptor(...interceptorArgs()); + expect(next.called).to.be.false; + }); + }); + + describe('with remaining bids', () => { + const REMAINING_BIDS = [{id: 1}, {id: 2}]; + beforeEach(() => { + interceptResult = {bids: REMAINING_BIDS, bidRequest: {bids: REMAINING_BIDS}}; + }); + + it('should call next', () => { + const callbacks = { + onResponse: {}, + onRequest: {}, + onBid: {} + }; + const args = interceptorArgs({cbs: callbacks}); + const expectedNextArgs = [ + args[2], + interceptResult.bids, + interceptResult.bidRequest, + ...args.slice(5, args.length - 1), + ].map(sinon.match.same) + .concat([sinon.match({ + onResponse: sinon.match.same(callbacks.onResponse), + onRequest: sinon.match.same(callbacks.onRequest), + onBid: sinon.match.same(callbacks.onBid) + })]); + bidderBidInterceptor(...args); + expect(next.calledOnceWith(...expectedNextArgs)).to.be.true; + }); + + it('should trigger onCompletion once both interceptBids.done and next.cbs.onCompletion are called ', () => { + bidderBidInterceptor(...interceptorArgs()); + expect(onCompletion.calledOnce).to.be.false; + next.args[0][next.args[0].length - 1].onCompletion(); + expect(onCompletion.calledOnce).to.be.false; + done(); + expect(onCompletion.calledOnce).to.be.true; + }); + }); +}); + +describe('pbsBidInterceptor', () => { + const EMPTY_INT_RES = {bids: [], bidRequest: {bids: []}}; + let next, interceptBids, s2sBidRequest, bidRequests, ajax, onResponse, onError, onBid, interceptResults, + addBids, dones, reqIdx; + + beforeEach(() => { + reqIdx = 0; + [addBids, dones] = [[], []]; + next = sinon.spy(); + ajax = sinon.spy(); + onResponse = sinon.spy(); + onError = sinon.spy(); + onBid = sinon.spy(); + interceptBids = sinon.stub().callsFake((opts) => { + addBids.push(opts.addBid); + dones.push(opts.done); + return interceptResults[reqIdx++]; + }); + s2sBidRequest = {}; + bidRequests = [{bids: []}, {bids: []}]; + interceptResults = [EMPTY_INT_RES, EMPTY_INT_RES]; + }); + + function callInterceptor() { + return pbsBidInterceptor(next, interceptBids, s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid}); + } + + it('passes addBids that trigger onBid', () => { + callInterceptor(); + bidRequests.forEach((_, i) => { + const bid = {adUnitCode: i, prop: i}; + const bidRequest = {req: i}; + addBids[i](bid, bidRequest); + expect(onBid.calledWith({adUnit: i, bid: sinon.match(bid)})); + }); + }); + + describe('on no match', () => { + it('should not call next', () => { + callInterceptor(); + expect(next.called).to.be.false; + }); + + it('should pass done callbacks that trigger a dummy onResponse once they all run', () => { + callInterceptor(); + expect(onResponse.called).to.be.false; + bidRequests.forEach((_, i) => { + dones[i](); + expect(onResponse.called).to.equal(i === bidRequests.length - 1); + }); + expect(onResponse.calledWith(true, [])).to.be.true; + }); + }); + + describe('on match', () => { + let matchingBids; + beforeEach(() => { + matchingBids = [ + [{bidId: 1, matching: true}, {bidId: 2, matching: true}], + [], + [{bidId: 3, matching: true}] + ]; + interceptResults = matchingBids.map((bids) => ({bids, bidRequest: {bids}})); + s2sBidRequest = { + ad_units: [ + {bids: [{bid_id: 1, matching: true}, {bid_id: 3, matching: true}, {bid_id: 100}, {bid_id: 101}]}, + {bids: [{bid_id: 2, matching: true}, {bid_id: 110}, {bid_id: 111}]}, + {bids: [{bid_id: 120}]} + ] + }; + bidRequests = matchingBids.map((mBids, i) => [ + {bidId: 100 + (i * 10)}, + {bidId: 101 + (i * 10)}, + ...mBids + ]); + }); + + it('should call next', () => { + callInterceptor(); + expect(next.calledOnceWith( + sinon.match.any, + sinon.match.any, + ajax, + sinon.match({ + onError, + onBid + }) + )).to.be.true; + }); + + it('should filter out intercepted bids from s2sBidRequest', () => { + callInterceptor(); + const interceptedS2SReq = next.args[0][0]; + const allMatching = interceptedS2SReq.ad_units.every((u) => u.bids.length > 0 && u.bids.every((b) => b.matching)); + expect(allMatching).to.be.true; + }); + + it('should pass bidRequests as returned by interceptBids', () => { + callInterceptor(); + const passedBidReqs = next.args[0][1]; + interceptResults + .filter((r) => r.bids.length > 0) + .forEach(({bidRequest}, i) => { + expect(passedBidReqs[i]).to.equal(bidRequest); + }); + }); + + it('should pass an onResponse that triggers original onResponse only once all intercept dones are called', () => { + callInterceptor(); + const interceptedOnResponse = next.args[0][next.args[0].length - 1].onResponse; + expect(onResponse.called).to.be.false; + const responseArgs = ['dummy', 'args']; + interceptedOnResponse(...responseArgs); + expect(onResponse.called).to.be.false; + dones.forEach((f, i) => { + f(); + expect(onResponse.called).to.equal(i === dones.length - 1); + }); + expect(onResponse.calledOnceWith(...responseArgs)).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js index fcf7056fb3f..d2a351b4089 100644 --- a/test/spec/modules/deepintentBidAdapter_spec.js +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -3,8 +3,8 @@ import {spec} from 'modules/deepintentBidAdapter.js'; import * as utils from '../../../src/utils.js'; describe('Deepintent adapter', function () { - let request; - let bannerResponse; + let request, videoBidRequests; + let bannerResponse, videoBidResponse, invalidResponse; beforeEach(function () { request = [ @@ -32,6 +32,38 @@ describe('Deepintent adapter', function () { } } ]; + videoBidRequests = + [ + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'deepintent', + bidId: '22bddb28db77d', + params: { + tagId: '100013', + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + testwrongparam: 3, + testwrongparam1: 'wrong', + minduration: 5, + maxduration: 30, + startdelay: 5, + playbackmethod: [1, 3], + api: [1, 2], + protocols: [2, 3], + battr: [13, 14], + minbitrate: 10, + maxbitrate: 10 + } + } + } + ]; bannerResponse = { 'body': { 'id': '303e1fae-9677-41e2-9a92-15a23445363f', @@ -53,7 +85,47 @@ describe('Deepintent adapter', function () { }], 'bidid': '0b08b09f-aaa1-4c14-b1c8-7debb1a7c1cd' } - } + }; + invalidResponse = { + 'body': { + 'id': '303e1fae-9677-41e2-9a92-15a23445363f', + 'seatbid': [{ + 'bid': [{ + 'id': '11447bb1-a266-470d-b0d7-8810f5b1b75f', + 'impid': 'a7e92b9b-d9db-4de8-9c3f-f90737335445', + 'price': 0.6, + 'adid': '10001', + 'adm': 'invalid response', + 'adomain': ['deepintent.com'], + 'cid': '103389', + 'crid': '13665', + 'w': 300, + 'h': 250, + 'dealId': 'dee_12312stdszzsx' + }], + 'seat': '10000' + }], + 'bidid': '0b08b09f-aaa1-4c14-b1c8-7debb1a7c1cd' + } + }; + videoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': 'Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://dsptracker.com/{PSPM}00:00:04https://www.deepintent.com', + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 6 + } + }] + }] + } + }; }); describe('validations', function () { @@ -88,6 +160,45 @@ describe('Deepintent adapter', function () { isValid = spec.isBidRequestValid(bid); expect(isValid).to.equals(false); }); + it('should check for context if video is present', function() { + let bid = { + bidder: 'deepintent', + params: { + tagId: '12345', + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + } + }, + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }); + it('should error out if context is not present and is Video', function() { + let bid = { + bidder: 'deepintent', + params: { + tagId: '12345', + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + } + }, + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }) }); describe('request check', function () { it('unmutaable bid request check', function () { @@ -179,6 +290,28 @@ describe('Deepintent adapter', function () { expect(data2.regs).to.equal(undefined); expect(data2.user.ext).to.equal(undefined); }); + it('bid request check: Video params check ', function() { + let bRequest = spec.buildRequests(videoBidRequests); + let data = JSON.parse(bRequest.data); + expect(data.imp[0].video).to.be.a('object'); + expect(data.imp[0].video.minduration).to.be.a('number'); + expect(data.imp[0].video.maxduration).to.be.a('number'); + expect(data.imp[0].video.startdelay).to.be.a('number'); + expect(data.imp[0].video.playbackmethod).to.be.an('array'); + expect(data.imp[0].video.api).to.be.an('array'); + expect(data.imp[0].video.protocols).to.be.an('array'); + expect(data.imp[0].video.battr).to.be.an('array'); + expect(data.imp[0].video.minbitrate).to.be.a('number'); + expect(data.imp[0].video.maxbitrate).to.be.a('number'); + expect(data.imp[0].video.w).to.be.a('number'); + }); + it('bid request param check : invalid video params', function() { + let bRequest = spec.buildRequests(videoBidRequests); + let data = JSON.parse(bRequest.data); + expect(data.imp[0].video).to.be.a('object'); + expect(data.imp[0].video.testwrongparam).to.equal(undefined); + expect(data.imp[0].video.testwrongparam1).to.equal(undefined); + }); }); describe('user sync check', function () { it('user sync url check', function () { @@ -202,10 +335,27 @@ describe('Deepintent adapter', function () { expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); expect(bResponse[0].currency).to.equal('USD'); expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('banner'); expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['deepintent.com']); expect(bResponse[0].ttl).to.equal(300); expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); }); + it('bid response check: valid video bid response', function() { + let request = spec.buildRequests(videoBidRequests); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].mediaType).to.equal('video'); + expect(response[0].vastXml).to.not.equal(undefined); + }); + it('invalid bid response check ', function() { + let bRequest = spec.buildRequests(request); + let response = spec.interpretResponse(invalidResponse, bRequest); + expect(response[0].mediaType).to.equal(undefined); + }); + it('invalid bid response check ', function() { + let bRequest = spec.buildRequests(videoBidRequests); + let response = spec.interpretResponse(invalidResponse, bRequest); + expect(response[0].mediaType).to.equal(undefined); + }); }) }); diff --git a/test/spec/modules/deltaprojectsBidAdapter_spec.js b/test/spec/modules/deltaprojectsBidAdapter_spec.js new file mode 100644 index 00000000000..382415eab62 --- /dev/null +++ b/test/spec/modules/deltaprojectsBidAdapter_spec.js @@ -0,0 +1,399 @@ +import { expect } from 'chai'; +import { + BIDDER_CODE, + BIDDER_ENDPOINT_URL, + spec, USERSYNC_URL, + getBidFloor +} from 'modules/deltaprojectsBidAdapter.js'; + +const BID_REQ_REFER = 'http://example.com/page?param=val'; + +describe('deltaprojectsBidAdapter', function() { + describe('isBidRequestValid', function () { + function makeBid() { + return { + bidder: BIDDER_CODE, + params: { + publisherId: '12345' + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }; + } + + it('should return true when bidder set correctly', function () { + expect(spec.isBidRequestValid(makeBid())).to.equal(true); + }); + + it('should return false when bid request is null', function () { + expect(spec.isBidRequestValid(undefined)).to.equal(false); + }); + + it('should return false when bidder not set correctly', function () { + let bid = makeBid(); + delete bid.bidder; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisher id is not set', function () { + let bid = makeBid(); + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const BIDREQ = { + bidder: BIDDER_CODE, + params: { + tagId: '403370', + siteId: 'example.com', + }, + sizes: [ + [300, 250], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + } + const bidRequests = [BIDREQ]; + const bannerRequest = spec.buildRequests(bidRequests, {refererInfo: { referer: BID_REQ_REFER }})[0]; + const bannerRequestBody = bannerRequest.data; + + it('send bid request with test tag if it is set in the param', function () { + const TEST_TAG = 1; + const bidRequest = Object.assign({}, BIDREQ, { + params: { ...BIDREQ.params, test: TEST_TAG }, + }); + const bidderRequest = { refererInfo: { referer: BID_REQ_REFER } }; + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + expect(request.data.test).to.equal(TEST_TAG); + }); + + it('send bid request with correct timeout', function () { + const TMAX = 10; + const bidderRequest = { refererInfo: { referer: BID_REQ_REFER }, timeout: TMAX }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.tmax).to.equal(TMAX); + }); + + it('send bid request to the correct endpoint URL', function () { + expect(bannerRequest.url).to.equal(BIDDER_ENDPOINT_URL); + }); + + it('sends bid request to our endpoint via POST', function () { + expect(bannerRequest.method).to.equal('POST'); + }); + + it('sends screen dimensions', function () { + expect(bannerRequestBody.device.w).to.equal(screen.width); + expect(bannerRequestBody.device.h).to.equal(screen.height); + }); + + it('includes the ad size in the bid request', function () { + expect(bannerRequestBody.imp[0].banner.format[0].w).to.equal(BIDREQ.sizes[0][0]); + expect(bannerRequestBody.imp[0].banner.format[0].h).to.equal(BIDREQ.sizes[0][1]); + }); + + it('sets domain and href correctly', function () { + expect(bannerRequestBody.site.domain).to.equal(BIDREQ.params.siteId); + expect(bannerRequestBody.site.page).to.equal(BID_REQ_REFER); + }); + + const gdprBidRequests = [{ + bidder: BIDDER_CODE, + params: { + tagId: '403370', + siteId: 'example.com' + }, + sizes: [ + [300, 250] + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }]; + const consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; + + const GDPR_REQ_REFERER = 'http://localhost:9876/' + function getGdprRequestBody(gdprApplies, consentString) { + const gdprRequest = spec.buildRequests(gdprBidRequests, { + gdprConsent: { + gdprApplies: gdprApplies, + consentString: consentString, + }, + refererInfo: { + referer: GDPR_REQ_REFERER, + }, + })[0]; + return gdprRequest.data; + } + + it('should handle gdpr applies being present and true', function() { + const gdprRequestBody = getGdprRequestBody(true, consentString); + expect(gdprRequestBody.regs.ext.gdpr).to.equal(1); + expect(gdprRequestBody.user.ext.consent).to.equal(consentString); + }) + + it('should handle gdpr applies being present and false', function() { + const gdprRequestBody = getGdprRequestBody(false, consentString); + expect(gdprRequestBody.regs.ext.gdpr).to.equal(0); + expect(gdprRequestBody.user.ext.consent).to.equal(consentString); + }) + + it('should handle gdpr applies being undefined', function() { + const gdprRequestBody = getGdprRequestBody(undefined, consentString); + expect(gdprRequestBody.regs).to.deep.equal({ext: {}}); + expect(gdprRequestBody.user.ext.consent).to.equal(consentString); + }) + + it('should handle gdpr consent being undefined', function() { + const gdprRequest = spec.buildRequests(gdprBidRequests, {refererInfo: { referer: GDPR_REQ_REFERER }})[0]; + const gdprRequestBody = gdprRequest.data; + expect(gdprRequestBody.regs).to.deep.equal({ ext: {} }); + expect(gdprRequestBody.user).to.deep.equal({ ext: {} }); + }) + }); + + describe('interpretResponse', function () { + const bidRequests = [ + { + bidder: BIDDER_CODE, + params: { + tagId: '403370', + siteId: 'example.com', + currency: 'USD', + }, + sizes: [ + [300, 250], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, + ]; + const request = spec.buildRequests(bidRequests, {refererInfo: { referer: BID_REQ_REFER }})[0]; + function makeResponse() { + return { + body: { + id: '5e5c23a5ba71e78', + seatbid: [ + { + bid: [ + { + id: '6vmb3isptf', + crid: 'deltaprojectscreative', + impid: '322add653672f68', + price: 1.22, + adm: '', + attr: [5], + h: 90, + nurl: 'http://nurl', + w: 728, + } + ], + seat: 'MOCK' + } + ], + bidid: '5e5c23a5ba71e78', + cur: 'USD' + } + }; + } + const expectedBid = { + requestId: '322add653672f68', + cpm: 1.22, + width: 728, + height: 90, + creativeId: 'deltaprojectscreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'banner', + ttl: 60, + ad: '
' + }; + + it('should get incorrect bid response if response body is missing', function () { + let response = makeResponse(); + delete response.body; + let result = spec.interpretResponse(response, request); + expect(result.length).to.equal(0); + }); + + it('should get incorrect bid response if id or seat id of response body is missing', function () { + let response1 = makeResponse(); + delete response1.body.id; + let result1 = spec.interpretResponse(response1, request); + expect(result1.length).to.equal(0); + + let response2 = makeResponse(); + delete response2.body.seatbid; + let result2 = spec.interpretResponse(response2, request); + expect(result2.length).to.equal(0); + }); + + it('should get the correct bid response', function () { + let result = spec.interpretResponse(makeResponse(), request); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedBid); + }); + + it('should handle a missing crid', function () { + let noCridResponse = makeResponse(); + delete noCridResponse.body.seatbid[0].bid[0].crid; + const fallbackCrid = noCridResponse.body.seatbid[0].bid[0].id; + let noCridResult = Object.assign({}, expectedBid, {'creativeId': fallbackCrid}); + let result = spec.interpretResponse(noCridResponse, request); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(noCridResult); + }); + + it('should handle a missing nurl', function () { + let noNurlResponse = makeResponse(); + delete noNurlResponse.body.seatbid[0].bid[0].nurl; + let noNurlResult = Object.assign({}, expectedBid); + noNurlResult.ad = ''; + let result = spec.interpretResponse(noNurlResponse, request); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(noNurlResult); + }); + + it('handles empty bid response', function () { + let response = { + body: { + id: '5e5c23a5ba71e78', + seatbid: [] + } + }; + let result = spec.interpretResponse(response, request); + expect(result.length).to.equal(0); + }); + + it('should keep custom properties', () => { + const customProperties = {test: 'a test message', param: {testParam: 1}}; + const expectedResult = Object.assign({}, expectedBid, {[spec.code]: customProperties}); + const response = makeResponse(); + response.body.seatbid[0].bid[0].ext = customProperties; + const result = spec.interpretResponse(response, request); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedResult); + }); + }); + + describe('onBidWon', function () { + const OPEN_RTB_RESP = { + body: { + id: 'abc', + seatbid: [ + { + bid: [ + { + 'id': 'abc*123*456', + 'impid': 'xxxxxxx', + 'price': 46.657196, + 'adm': '', - 'adid': 'ad-2.6.75.300x250', - 'price': 1.5, - 'w': 300, - 'h': 250, - 'cid': '2.6.75', - 'crid': '2.6.75.300x250', - 'dealid': 'default' - }], - 'seat': '2' - }] -}; - -const bidContentExt = { - ...bidContent, - ext: { - 'syncUrl': 'https://sync.meazy.co/sync/img?api_key=6910b7344ae566a1' - } -}; - -const bidResponse = { - body: bidContent -}; - -const noBidResponse = { body: {'nbr': 2} }; - -describe('meazyBidAdapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - it('should return false', function () { - let bid = Object.assign({}, bidRequest); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return true', function () { - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); - }); - }); - - describe('buildRequests', function () { - it('should format valid url', function () { - const request = spec.buildRequests([bidRequest], bidderRequest); - expect(request.url).to.equal(VALID_ENDPOINT); - }); - - it('should format valid url', function () { - const request = spec.buildRequests([bidRequest], bidderRequest); - expect(request.url).to.equal(VALID_ENDPOINT); - }); - - it('should format valid request body', function () { - const request = spec.buildRequests([bidRequest], bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.id).to.exist; - expect(payload.imp).to.exist; - expect(payload.imp[0]).to.exist; - expect(payload.imp[0].banner).to.exist; - expect(payload.imp[0].banner.format).to.exist; - expect(payload.device).to.exist; - expect(payload.site).to.exist; - expect(payload.site.domain).to.exist; - expect(payload.cur).to.exist; - }); - - it('should format valid url', function () { - const request = spec.buildRequests([bidRequest], bidderRequest); - expect(request.url).to.equal(VALID_ENDPOINT); - }); - - it('should not fill user.ext object', function () { - const request = spec.buildRequests([bidRequest], bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.user.ext).to.equal(undefined); - }); - - it('should fill user.ext object', function () { - const consentString = 'hellogdpr'; - const request = spec.buildRequests([bidRequest], { ...bidderRequest, gdprConsent: { gdprApplies: true, consentString } }); - const payload = JSON.parse(request.data); - expect(payload.user.ext).to.exist.and.to.be.a('object'); - expect(payload.user.ext.consent).to.equal(consentString); - expect(payload.user.ext.gdpr).to.equal(1); - }); - }); - - describe('interpretResponse', function () { - it('should get correct bid response', function () { - const result = spec.interpretResponse(bidResponse); - const validResponse = [{ - requestId: '30b31c1838de1e', - cpm: 1.5, - width: 300, - height: 250, - creativeId: '2.6.75.300x250', - netRevenue: true, - dealId: 'default', - currency: 'USD', - ttl: 900, - ad: '' - }]; - - expect(result).to.deep.equal(validResponse); - }); - - it('handles nobid responses', function () { - let result = spec.interpretResponse(noBidResponse); - expect(result.length).to.equal(0); - }); - }); - - describe('getUserSyncs', function () { - const syncOptionsFF = { iframeEnabled: false }; - const syncOptionsEF = { iframeEnabled: true }; - const syncOptionsEE = { pixelEnabled: true, iframeEnabled: true }; - const syncOptionsFE = { pixelEnabled: true, iframeEnabled: false }; - - const successIFrame = { type: 'iframe', url: 'https://sync.meazy.co/sync/iframe' }; - const successPixel = { type: 'image', url: 'https://sync.meazy.co/sync/img?api_key=6910b7344ae566a1' }; - - it('should return an empty array', function () { - expect(spec.getUserSyncs(syncOptionsFF, [])).to.be.empty; - expect(spec.getUserSyncs(syncOptionsFF, [ bidResponse ])).to.be.empty; - expect(spec.getUserSyncs(syncOptionsFE, [ bidResponse ])).to.be.empty; - }); - - it('should be equal to the expected result', function () { - expect(spec.getUserSyncs(syncOptionsEF, [ bidResponse ])).to.deep.equal([successIFrame]); - expect(spec.getUserSyncs(syncOptionsFE, [ { body: bidContentExt } ])).to.deep.equal([successPixel]); - expect(spec.getUserSyncs(syncOptionsEE, [ { body: bidContentExt } ])).to.deep.equal([successPixel, successIFrame]); - expect(spec.getUserSyncs(syncOptionsEE, [])).to.deep.equal([successIFrame]); - }) - }); -}); diff --git a/test/spec/modules/mediaforceBidAdapter_spec.js b/test/spec/modules/mediaforceBidAdapter_spec.js index 24326afe5c0..0b5c4d00f53 100644 --- a/test/spec/modules/mediaforceBidAdapter_spec.js +++ b/test/spec/modules/mediaforceBidAdapter_spec.js @@ -27,7 +27,7 @@ describe('mediaforce bid adapter', function () { bidder: 'mediaforce', params: { property: '10433394', - bidfloor: 0.3, + bidfloor: 0, }, }; @@ -157,7 +157,7 @@ describe('mediaforce bid adapter', function () { it('should return proper banner imp', function () { let bid = utils.deepClone(defaultBid); - bid.params.bidfloor = 0.5; + bid.params.bidfloor = 0; let bidRequests = [bid]; let bidderRequest = { @@ -219,7 +219,7 @@ describe('mediaforce bid adapter', function () { assert.deepEqual(request, { method: 'POST', url: requestUrl, - data: '{"id":"' + data.id + '","site":{"page":"' + pageUrl + '","ref":"https%3A%2F%2Fwww.prebid.org","id":"pub123","publisher":{"id":"pub123"}},"device":{"ua":"' + navigator.userAgent + '","js":1,"dnt":' + dnt + ',"language":"' + language + '"},"ext":{"mediaforce":{"hb_key":"210a474e-88f0-4646-837f-4253b7cf14fb"}},"tmax":1500,"imp":[{"tagid":"202","secure":' + secure + ',"bidfloor":0.5,"ext":{"mediaforce":{"transactionId":"d45dd707-a418-42ec-b8a7-b70a6c6fab0b"}},"banner":{"w":300,"h":250},"native":{"ver":"1.2","request":{"assets":[{"required":1,"id":1,"title":{"len":800}},{"required":1,"id":3,"img":{"type":3,"w":300,"h":250}},{"required":1,"id":5,"data":{"type":1}}],"context":1,"plcmttype":1,"ver":"1.2"}}}]}', + data: '{"id":"' + data.id + '","site":{"page":"' + pageUrl + '","ref":"https%3A%2F%2Fwww.prebid.org","id":"pub123","publisher":{"id":"pub123"}},"device":{"ua":"' + navigator.userAgent + '","js":1,"dnt":' + dnt + ',"language":"' + language + '"},"ext":{"mediaforce":{"hb_key":"210a474e-88f0-4646-837f-4253b7cf14fb"}},"tmax":1500,"imp":[{"tagid":"202","secure":' + secure + ',"bidfloor":0,"ext":{"mediaforce":{"transactionId":"d45dd707-a418-42ec-b8a7-b70a6c6fab0b"}},"banner":{"w":300,"h":250},"native":{"ver":"1.2","request":{"assets":[{"required":1,"id":1,"title":{"len":800}},{"required":1,"id":3,"img":{"type":3,"w":300,"h":250}},{"required":1,"id":5,"data":{"type":1}}],"context":1,"plcmttype":1,"ver":"1.2"}}}]}', }); }); @@ -391,6 +391,7 @@ describe('mediaforce bid adapter', function () { mediaType: BANNER, requestId: bid.impid, ttl: 300, + meta: { advertiserDomains: [] }, width: bid.w, }])); }); @@ -477,6 +478,7 @@ describe('mediaforce bid adapter', function () { mediaType: NATIVE, requestId: bid.impid, ttl: 300, + meta: { advertiserDomains: [] }, }])); }); }); @@ -560,6 +562,7 @@ describe('mediaforce bid adapter', function () { mediaType: NATIVE, requestId: bid.impid, ttl: 300, + meta: { advertiserDomains: [] }, }])); }); }); diff --git a/test/spec/modules/mediagoBidAdapter_spec.js b/test/spec/modules/mediagoBidAdapter_spec.js deleted file mode 100644 index 25cfc72672b..00000000000 --- a/test/spec/modules/mediagoBidAdapter_spec.js +++ /dev/null @@ -1,101 +0,0 @@ -import { - expect -} from 'chai'; -import { - spec -} from 'modules/mediagoBidAdapter.js'; - -describe('mediago:BidAdapterTests', function() { - let bidRequestData = { - 'bidderCode': 'mediago', - 'auctionId': '7fae02a9-0195-472f-ba94-708d3bc2c0d9', - 'bidderRequestId': '4fec04e87ad785', - 'bids': [{ - 'bidder': 'mediago', - 'params': { - 'token': '85a6b01e41ac36d49744fad726e3655d' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } - }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '5e24a2ce-db03-4565-a8a3-75dbddca9377', - 'sizes': [ - [ - 300, - 250 - ] - ], - 'bidId': '54d73f19c9d47a', - 'bidderRequestId': '4fec04e87ad785', - 'auctionId': '7fae02a9-0195-472f-ba94-708d3bc2c0d9', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }] - }; - let request = []; - - it('mediago:validate_pub_params', function() { - expect( - spec.isBidRequestValid({ - bidder: 'mediago', - params: { - token: ['85a6b01e41ac36d49744fad726e3655d'] - } - }) - ).to.equal(true); - }); - - it('mediago:validate_generated_params', function() { - request = spec.buildRequests(bidRequestData.bids, bidRequestData); - // console.log(request); - let req_data = JSON.parse(request.data); - expect(req_data.imp).to.have.lengthOf(1); - }); - - it('mediago:validate_response_params', function() { - let serverResponse = { - body: { - 'id': '7244645c-a81a-4760-8fd6-9d908d2c4a44', - 'seatbid': [{ - 'bid': [{ - 'id': 'aa86796a857ebedda9a2d7128a87dab1', - 'impid': '1', - 'price': 0.05, - 'nurl': 'http://d21uzv52i0cqie.cloudfront.net/api/winnotice?tn=341443089c0cb829164455a42d216ee3\u0026winloss=1\u0026id=aa86796a857ebedda9a2d7128a87dab1\u0026seat_id=${AUCTION_SEAT_ID}\u0026currency=${AUCTION_CURRENCY}\u0026bid_id=${AUCTION_BID_ID}\u0026ad_id=${AUCTION_AD_ID}\u0026loss=${AUCTION_LOSS}\u0026imp_id=1\u0026price=${AUCTION_PRICE}\u0026test=0\u0026time=1597714943\u0026adp=Dtnz0O4U8sdAU-XGGijCAgMbjDIeMGyCLXeSg1laXxM\u0026dsp_id=22\u0026url=-BFDu2NYtc4PYTplFW_2JcnDSRVLOOfaERbwJABjFyG6NUB4ywA3dUaXt5zPlyCUpBCOxjH9gk4E6yWTshzuSfQSx7g_TxvcXYUgh7YtY9NQZxx14InmNCTsezqID5UztV7llz8SXWHQ-ZsutH1nJIZzl1jH3i2uCPi91shqIZLN1bLJ5guAr5O4WyxVeOqIKyD_GiVcY9Olm51iI_3wgwFyDEN_dIDv-ObgNxpbPD0L11-62bjhGw3__7RuEo6XLdox-g46Fcqk6i0zayfsPM4QeMAhWJ4lsg-xswSI0YAfzyoOIeTWB78mdpt_GmN5PKZZPqyO7VkbwHEasn-mTyYTddbz5v2fzEkRO0AQZtAZx96PANGrNvcOHnRVmCdkzN96b5Ur1_8ipdyzHOFRtJ-z_KmKaxig6himvMCePozZvrvihiGhigP4RGiFT7ytVYKHyUGAV2PF5SwtgnB0uGCltd7o1CLhZyZEQNgE7LSESyGztZ5kM9N_VZV9gPZVhvlJDfYFNRW9i6D2pZxV0Gd63rA9gpeUJ3mhbkj-B27VRKrNTBSrwIAU7P0RPD5_opl3G8nPD1Ce2vKuQK8qynHWQblfeA61nDok-fRezSKbzwepqi8oxXadFrCmN7KxP_mPqA794xYzIw5-mS64NA', - 'burl': 'http://d21uzv52i0cqie.cloudfront.net/api/winnotice?tn=341443089c0cb829164455a42d216ee3\u0026winloss=2\u0026id=aa86796a857ebedda9a2d7128a87dab1\u0026seat_id=${AUCTION_SEAT_ID}\u0026currency=${AUCTION_CURRENCY}\u0026bid_id=${AUCTION_BID_ID}\u0026ad_id=${AUCTION_AD_ID}\u0026loss=${AUCTION_LOSS}\u0026imp_id=1\u0026price=${AUCTION_PRICE}\u0026test=0\u0026time=1597714943\u0026adp=Dtnz0O4U8sdAU-XGGijCAgMbjDIeMGyCLXeSg1laXxM\u0026dsp_id=22\u0026url=dXerAvyp4zYQzsQ56eGB4JtiA4yFaYlTqcHffccrvCg', - 'lurl': 'http://d21uzv52i0cqie.cloudfront.net/api/winnotice?tn=341443089c0cb829164455a42d216ee3\u0026winloss=0\u0026id=aa86796a857ebedda9a2d7128a87dab1\u0026seat_id=${AUCTION_SEAT_ID}\u0026currency=${AUCTION_CURRENCY}\u0026bid_id=${AUCTION_BID_ID}\u0026ad_id=${AUCTION_AD_ID}\u0026loss=${AUCTION_LOSS}\u0026imp_id=1\u0026price=${AUCTION_PRICE}\u0026test=0\u0026time=1597714943\u0026adp=Dtnz0O4U8sdAU-XGGijCAgMbjDIeMGyCLXeSg1laXxM\u0026dsp_id=22\u0026url=ptSxg_vR7-fdx-WAkkUADJb__BntE5a6-RSeYdUewvk', - 'adm': '\u003clink rel=\"stylesheet\" href=\"//cdn.mediago.io/js/style/style_banner_300*250.css\"\u003e\u003cdiv id=\"mgcontainer-583ce3286b442001205b2fb9a5488efc\" class=\"mediago-placement imgTopTitleBottom\" style=\"position:relative;width:298px;height:248px;overflow:hidden\"\u003e\u003ca href=\"http://trace.mediago.io/api/bidder/track?tn=341443089c0cb829164455a42d216ee3\u0026price=PRMC8pCHtH55ipgXubUs8jlsKTBxWRSEweH8Mee_aUQ\u0026evt=102\u0026rid=aa86796a857ebedda9a2d7128a87dab1\u0026campaignid=1003540\u0026impid=25-300x175-1\u0026offerid=1107782\u0026test=0\u0026time=1597714943\u0026cp=GJA03gjm-ugPTmN7prOpvhfu1aA04IgpTcW4oRX22Lg\u0026clickid=25_aa86796a857ebedda9a2d7128a87dab1_25-300x175-1\u0026acid=164\u0026trackingid=583ce3286b442001205b2fb9a5488efc\u0026uid=6dda6c6b70eb4e2d9ab3469d921f2c74\u0026jt=2\u0026url=PQFFci337KgCVkx7KTgRItClLaWH0lgTcIlgBRTpfKngVJES4uKLfxXz9mjLcDWIbWQcEVVk_gfTcIaK8oKO2YyVG5lc3hjZeZr0VaIDHbWggPJaqtfDK9T0HZIKvrpe\" target=\"_blank\"\u003e\u003cimg alt=\"Robert Vowed To Keep Silent, But Decided To Put The People First And Speak\" src=\"https://d2cli4kgl5uxre.cloudfront.net/ML/b5e361889beef5eaf69987384b7a56e8__300x175.png\" style=\"height:70%;width:100%;border-width:0;border:none;\"\u003e\u003ch3 class=\"title\" style=\"font-size:16px;\"\u003eRobert Vowed To Keep Silent, But Decided To Put The People First And Speak\u003c/h3\u003e\u003c/a\u003e\u003cspan class=\"source\"\u003e\u003ca class=\"sourcename\" href=\"//www.mediago.io\" target=\"_blank\"\u003e\u003cspan\u003eAd\u003c/span\u003e \u003c/a\u003e\u003ca class=\"srcnameadslabelurl\" href=\"//www.mediago.io/privacy\" target=\"_blank\"\u003e\u003cspan\u003eViral Net News\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e\u003c/div\u003e\u003cscript\u003e!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){\"undefined\"!=typeof Symbol\u0026\u0026Symbol.toStringTag\u0026\u0026Object.defineProperty(e,Symbol.toStringTag,{value:\"Module\"}),Object.defineProperty(e,\"__esModule\",{value:!0})},n.t=function(e,t){if(1\u0026t\u0026\u0026(e=n(e)),8\u0026t)return e;if(4\u0026t\u0026\u0026\"object\"==typeof e\u0026\u0026e\u0026\u0026e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,\"default\",{enumerable:!0,value:e}),2\u0026t\u0026\u0026\"string\"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e\u0026\u0026e.__esModule?function(){return e.default}:function(){return e};return n.d(t,\"a\",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p=\"\",n(n.s=24)}({24:function(e,t,n){\"use strict\";function o(e){var t=new Image;t.src=e,t.style=\"display:none;visibility:hidden\",t.width=0,t.height=0,document.body.appendChild(t)}o(\"http://d21uzv52i0cqie.cloudfront.net/api/bidder/track?tn=341443089c0cb829164455a42d216ee3\u0026price=PRMC8pCHtH55ipgXubUs8jlsKTBxWRSEweH8Mee_aUQ\u0026evt=101\u0026rid=aa86796a857ebedda9a2d7128a87dab1\u0026campaignid=1003540\u0026impid=25-300x175-1\u0026offerid=1107782\u0026test=0\u0026time=1597714943\u0026cp=GJA03gjm-ugPTmN7prOpvhfu1aA04IgpTcW4oRX22Lg\u0026acid=164\u0026trackingid=583ce3286b442001205b2fb9a5488efc\u0026uid=6dda6c6b70eb4e2d9ab3469d921f2c74\");var r=document.getElementById(\"mgcontainer-583ce3286b442001205b2fb9a5488efc\"),i=!1;!function e(){setTimeout((function(){var t,n;!i\u0026\u0026(t=r,n=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight,(t.getBoundingClientRect()\u0026\u0026t.getBoundingClientRect().top)\u003c=n-.75*(t.offsetHeight||t.clientHeight))?(i=!0,o(\"http://d21uzv52i0cqie.cloudfront.net/api/bidder/track?tn=341443089c0cb829164455a42d216ee3\u0026price=PRMC8pCHtH55ipgXubUs8jlsKTBxWRSEweH8Mee_aUQ\u0026evt=104\u0026rid=aa86796a857ebedda9a2d7128a87dab1\u0026campaignid=1003540\u0026impid=25-300x175-1\u0026offerid=1107782\u0026test=0\u0026time=1597714943\u0026cp=GJA03gjm-ugPTmN7prOpvhfu1aA04IgpTcW4oRX22Lg\u0026acid=164\u0026trackingid=583ce3286b442001205b2fb9a5488efc\u0026uid=6dda6c6b70eb4e2d9ab3469d921f2c74\u0026sid=16__11__13\u0026format=\u0026crid=b5e361889beef5eaf69987384b7a56e8\")):e()}),500)}()}});\u003c/script\u003e\u003cscript type=\"text/javascript\" src=\"http://d21uzv52i0cqie.cloudfront.net/api/track?tn=341443089c0cb829164455a42d216ee3\u0026price=${AUCTION_PRICE}\u0026evt=5\u0026rid=aa86796a857ebedda9a2d7128a87dab1\u0026impid=1\u0026offerid=\u0026tagid=\u0026test=0\u0026time=1597714943\u0026adp=5zrCZ2rC-WLafYkNpeTnzA72tDqVZUlOA3Js6_eJjYU\u0026dsp_id=22\u0026cp=${cp}\u0026url=\u0026type=script\"\u003e\u003c/script\u003e\u003cscript\u003edocument.addEventListener\u0026\u0026document.addEventListener(\"click\",function(){var a=document.createElement(\"script\");a.src=\"http://d21uzv52i0cqie.cloudfront.net/api/track?tn=341443089c0cb829164455a42d216ee3\u0026price=${AUCTION_PRICE}\u0026evt=6\u0026rid=aa86796a857ebedda9a2d7128a87dab1\u0026impid=1\u0026offerid=\u0026tagid=\u0026test=0\u0026time=1597714943\u0026adp=5zrCZ2rC-WLafYkNpeTnzA72tDqVZUlOA3Js6_eJjYU\u0026dsp_id=22\u0026cp=${cp}\u0026url=\u0026clickid=25_aa86796a857ebedda9a2d7128a87dab1_1\";document.body.appendChild(a)});\u003c/script\u003e', - 'cid': '1003540', - 'crid': 'b5e361889beef5eaf69987384b7a56e8', - 'w': 300, - 'h': 250 - }] - }], - 'cur': 'USD' - } - }; - - let bids = spec.interpretResponse(serverResponse); - // console.log({ - // bids - // }); - expect(bids).to.have.lengthOf(1); - - let bid = bids[0]; - - expect(bid.creativeId).to.equal('b5e361889beef5eaf69987384b7a56e8'); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.currency).to.equal('USD'); - }); -}); diff --git a/test/spec/modules/mediakeysBidAdapter_spec.js b/test/spec/modules/mediakeysBidAdapter_spec.js new file mode 100644 index 00000000000..10869f5e35e --- /dev/null +++ b/test/spec/modules/mediakeysBidAdapter_spec.js @@ -0,0 +1,897 @@ +import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; +import { spec } from 'modules/mediakeysBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; +import { OUTSTREAM } from '../../../src/video.js'; + +describe('mediakeysBidAdapter', function () { + const adapter = newBidder(spec); + let utilsMock; + let sandbox; + + const bid = { + bidder: 'mediakeys', + params: {}, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: '47789656-9e5c-4250-b7e0-2ce4cbe71a55', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '299320f4de980d', + bidderRequestId: '1c1b642f803242', + auctionId: '84212956-c377-40e8-b000-9885a06dc692', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + ortb2Imp: { + ext: { data: { something: 'test' } } + } + }; + + const bidNative = { + bidder: 'mediakeys', + params: {}, + mediaTypes: { + native: { + body: { + required: true + }, + title: { + required: true, + len: 800 + }, + sponsoredBy: { + required: true + }, + body2: { + required: true + }, + image: { + required: true, + sizes: [[300, 250], [300, 600], [100, 150]], + }, + icon: { + required: true, + sizes: [50, 50], + }, + }, + }, + nativeParams: { + body: { + required: true + }, + title: { + required: true, + len: 800 + }, + sponsoredBy: { + required: true + }, + body2: { + required: true + }, + image: { + required: true, + sizes: [[300, 250], [300, 600], [100, 150]], + }, + icon: { + required: true, + sizes: [50, 50], + }, + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: '47789656-9e5c-4250-b7e0-2ce4cbe71a55', + bidId: '299320f4de980d', + bidderRequestId: '1c1b642f803242', + auctionId: '84212956-c377-40e8-b000-9885a06dc692', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + ortb2Imp: { + ext: { data: { something: 'test' } } + } + }; + + const bidVideo = { + bidder: 'mediakeys', + params: {}, + mediaTypes: { + video: { + context: OUTSTREAM, + playerSize: [480, 320] + } + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: '47789656-9e5c-4250-b7e0-2ce4cbe71a55', + bidId: '299320f4de980d', + bidderRequestId: '1c1b642f803242', + auctionId: '84212956-c377-40e8-b000-9885a06dc692', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + ortb2Imp: { + ext: { data: { something: 'test' } } + } + }; + + const bidderRequest = { + bidderCode: 'mediakeys', + auctionId: '84212956-c377-40e8-b000-9885a06dc692', + bidderRequestId: '1c1b642f803242', + bids: [ + bid + ], + auctionStart: 1620973766319, + timeout: 1000, + refererInfo: { + referer: + 'https://local.url/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://local.url/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + ], + canonicalUrl: null, + }, + start: 1620973766325, + }; + + beforeEach(function () { + utilsMock = sinon.mock(utils); + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + utilsMock.restore(); + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + it('should returns true when bid is provided even with empty params', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should returns false when bid is falsy or empty', function () { + const emptyBid = {}; + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid(false)).to.equal(false); + expect(spec.isBidRequestValid(emptyBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('should create imp for supported mediaType only', function() { + const bidRequests = [utils.deepClone(bid)]; + const bidderRequestCopy = utils.deepClone(bidderRequest); + bidderRequestCopy.bids = bidRequests; + + bidRequests[0].mediaTypes.video = { + playerSize: [300, 250], + context: OUTSTREAM + } + + bidRequests[0].mediaTypes.native = bidNative.mediaTypes.native; + bidRequests[0].nativeParams = bidNative.mediaTypes.native; + + bidderRequestCopy.bids = bidRequests[0]; + + const request = spec.buildRequests(bidRequests, bidderRequestCopy); + const data = request.data; + + expect(data.imp.length).to.equal(1); + expect(data.imp[0].banner).to.exist; + expect(data.imp[0].video).to.exist; + expect(data.imp[0].native).to.exist; + }); + + it('should get expected properties with default values (no params set)', function () { + const bidRequests = [utils.deepClone(bid)]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const data = request.data; + + // openRTB 2.5 + expect(data.at).to.equal(1); + expect(data.cur[0]).to.equal('USD'); // default currency + expect(data.source.tid).to.equal(bidderRequest.auctionId); + + expect(data.imp.length).to.equal(1); + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); + expect(data.imp[0].banner.w).to.equal(300); + expect(data.imp[0].banner.h).to.equal(250); + expect(data.imp[0].banner.format[0].w).to.equal(300); + expect(data.imp[0].banner.format[0].h).to.equal(250); + expect(data.imp[0].banner.format[1].w).to.equal(300); + expect(data.imp[0].banner.format[1].h).to.equal(600); + expect(data.imp[0].banner.topframe).to.equal(0); + expect(data.imp[0].banner.pos).to.equal(0); + + // Ortb2Imp ext + expect(data.imp[0].ext).to.exist; + expect(data.imp[0].ext.data.something).to.equal('test'); + }); + + describe('native imp', function() { + it('should get a native object in request', function() { + const bidRequests = [utils.deepClone(bidNative)]; + const bidderRequestCopy = utils.deepClone(bidderRequest); + bidderRequestCopy.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequestCopy); + const data = request.data; + + expect(data.imp.length).to.equal(1); + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); + expect(data.imp[0].native).to.exist; + expect(data.imp[0].native.request.ver).to.equal('1.2'); + expect(data.imp[0].native.request.context).to.equal(1); + expect(data.imp[0].native.request.plcmttype).to.equal(1); + expect(data.imp[0].native.request.assets.length).to.equal(6); + // find the asset body + const bodyAsset = find(data.imp[0].native.request.assets, asset => asset.id === 6); + expect(bodyAsset.data.type).to.equal(2); + }); + + it('should get a native object in request with properties filled with params values', function() { + const bidRequests = [utils.deepClone(bidNative)]; + bidRequests[0].params = { + native: { + context: 3, + plcmttype: 3, + } + } + const bidderRequestCopy = utils.deepClone(bidderRequest); + bidderRequestCopy.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequestCopy); + const data = request.data; + + expect(data.imp.length).to.equal(1); + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); + expect(data.imp[0].native).to.exist; + expect(data.imp[0].native.request.ver).to.equal('1.2'); + expect(data.imp[0].native.request.context).to.equal(3); + expect(data.imp[0].native.request.plcmttype).to.equal(3); + expect(data.imp[0].native.request.assets.length).to.equal(6); + }); + + it('should get a native object in request when native type ,image" has been set', function() { + const bidRequests = [utils.deepClone(bidNative)]; + bidRequests[0].mediaTypes.native = { type: 'image' }; + bidRequests[0].nativeParams = { + image: { required: true }, + title: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true }, // [1] Will be ignored as it is used in response validation only + body: { required: false }, + icon: { required: false }, + }; + + const bidderRequestCopy = utils.deepClone(bidderRequest); + bidderRequestCopy.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequestCopy); + const data = request.data; + + expect(data.imp.length).to.equal(1); + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); + expect(data.imp[0].native).to.exist; + expect(data.imp[0].native.request.ver).to.equal('1.2'); + expect(data.imp[0].native.request.context).to.equal(1); + expect(data.imp[0].native.request.plcmttype).to.equal(1); + expect(data.imp[0].native.request.assets.length).to.equal(5); // [1] clickUrl ignored + }); + + it('should log errors and ignore misformated assets', function() { + const bidRequests = [utils.deepClone(bidNative)]; + delete bidRequests[0].nativeParams.title.len; + bidRequests[0].nativeParams.unregistred = {required: true}; + + const bidderRequestCopy = utils.deepClone(bidderRequest); + bidderRequestCopy.bids = bidRequests; + + utilsMock.expects('logWarn').twice(); + + const request = spec.buildRequests(bidRequests, bidderRequestCopy); + const data = request.data; + + expect(data.imp.length).to.equal(1); + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); + expect(data.imp[0].native).to.exist; + expect(data.imp[0].native.request.assets.length).to.equal(5); + }); + }); + + describe('video imp', function() { + it('should get a video object in request', function() { + const bidRequests = [utils.deepClone(bidVideo)]; + const bidderRequestCopy = utils.deepClone(bidderRequest); + bidderRequestCopy.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequestCopy); + const data = request.data; + + expect(data.imp.length).to.equal(1); + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); + expect(data.imp[0].banner).to.not.exist; + expect(data.imp[0].video).to.exist; + expect(data.imp[0].video.w).to.equal(480); + expect(data.imp[0].video.h).to.equal(320); + }); + + it('should ignore and warn misformated ORTB video properties', function() { + const bidRequests = [utils.deepClone(bidVideo)]; + bidRequests[0].mediaTypes.video.unknown = 'foo'; + bidRequests[0].mediaTypes.video.placement = 10; + bidRequests[0].mediaTypes.video.skipmin = 5; + const bidderRequestCopy = utils.deepClone(bidderRequest); + bidderRequestCopy.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequestCopy); + const data = request.data; + + expect(data.imp.length).to.equal(1); + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); + expect(data.imp[0].banner).to.not.exist; + expect(data.imp[0].video).to.exist; + expect(data.imp[0].video.w).to.equal(480); + expect(data.imp[0].video.h).to.equal(320); + expect(data.imp[0].video.skipmin).to.equal(5); + expect(data.imp[0].video.placement).to.not.exist; + expect(data.imp[0].video.unknown).to.not.exist; + }); + + it('should merge adUnit mediaTypes level and bidder level params properties ', function() { + const bidRequests = [utils.deepClone(bidVideo)]; + bidRequests[0].mediaTypes.video.placement = 1; + bidRequests[0].mediaTypes.video.mimes = ['video/mpeg4']; + bidRequests[0].mediaTypes.video.protocols = [1]; + bidRequests[0].mediaTypes.video.minduration = 10; + bidRequests[0].mediaTypes.video.maxduration = 45; + bidRequests[0].mediaTypes.video.skipmin = 5; + bidRequests[0].mediaTypes.video.sequence = 3; + bidRequests[0].mediaTypes.video.linearity = 1; + bidRequests[0].mediaTypes.video.battr = [12]; + bidRequests[0].mediaTypes.video.maxextended = 10; + bidRequests[0].mediaTypes.video.minbitrate = 720; + bidRequests[0].mediaTypes.video.maxbitrate = 720; + bidRequests[0].mediaTypes.video.boxingallowed = 1; + bidRequests[0].mediaTypes.video.playbackmethod = [1]; + bidRequests[0].mediaTypes.video.playbackend = 2; + bidRequests[0].mediaTypes.video.delivery = 2; + bidRequests[0].mediaTypes.video.pos = 0; + bidRequests[0].mediaTypes.video.companionad = [{ w: 360, h: 80 }] + bidRequests[0].mediaTypes.video.api = [1]; + bidRequests[0].mediaTypes.video.companiontype = [1]; + + // bidder level + bidRequests[0].params.video = { + pos: 2, // override + skip: 1, + skipafter: 10, + startdelay: 3 + }; + + const bidderRequestCopy = utils.deepClone(bidderRequest); + bidderRequestCopy.bids = bidRequests; + + utilsMock.expects('logWarn').never(); + + const request = spec.buildRequests(bidRequests, bidderRequestCopy); + const data = request.data; + + expect(data.imp.length).to.equal(1); + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); + expect(data.imp[0].banner).to.not.exist; + expect(data.imp[0].video).to.exist; + + expect(Object.keys(data.imp[0].video).length).to.equal(23); // 21 ortb params (2 skipped) + computed width/height. + expect(data.imp[0].video.w).to.equal(480); + expect(data.imp[0].video.h).to.equal(320); + expect(data.imp[0].video.mimes[0]).to.equal('video/mpeg4'); + expect(data.imp[0].video.pos).to.equal(2); + expect(data.imp[0].video.skip).to.equal(1); + expect(data.imp[0].video.skipafter).to.equal(10); + expect(data.imp[0].video.startdelay).to.equal(3); + expect(data.imp[0].video.companionad).to.not.exist; + expect(data.imp[0].video.companiontype).to.not.exist; + }); + + it('should log warn message when OpenRTB validation fails ', function() { + const bidRequests = [utils.deepClone(bidVideo)]; + bidRequests[0].mediaTypes.video.placement = 'string'; + bidRequests[0].mediaTypes.video.api = 1; + const bidderRequestCopy = utils.deepClone(bidderRequest); + bidderRequestCopy.bids = bidRequests; + + utilsMock.expects('logWarn').twice(); + + spec.buildRequests(bidRequests, bidderRequestCopy); + }); + }); + + it('should get expected properties with values from params', function () { + const bidRequests = [utils.deepClone(bid)]; + bidRequests[0].params = { + pos: 2, + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const data = request.data; + expect(data.imp[0].banner.pos).to.equal(2); + }); + + it('should get expected properties with schain', function () { + const schain = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'ssp.test', + sid: '00001', + hp: 1, + }, + ], + }; + const bidRequests = [utils.deepClone(bid)]; + bidRequests[0].schain = schain; + const request = spec.buildRequests(bidRequests, bidderRequest); + const data = request.data; + expect(data.source.ext.schain).to.equal(schain); + }); + + it('should get expected properties with coppa', function () { + sinon.stub(config, 'getConfig').withArgs('coppa').returns(true); + + const bidRequests = [utils.deepClone(bid)]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const data = request.data; + expect(data.regs.coppa).to.equal(1); + + config.getConfig.restore(); + }); + + it('should get expected properties with US privacy', function () { + const consent = 'Y11N'; + const bidRequests = [utils.deepClone(bid)]; + const bidderRequestWithUsPrivcay = utils.deepClone(bidderRequest); + bidderRequestWithUsPrivcay.uspConsent = consent; + const request = spec.buildRequests( + bidRequests, + bidderRequestWithUsPrivcay + ); + const data = request.data; + expect(data.regs.ext.us_privacy).to.equal(consent); + }); + + it('should get expected properties with GDPR', function () { + const consent = { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true, + }; + const bidRequests = [utils.deepClone(bid)]; + const bidderRequestWithGDPR = utils.deepClone(bidderRequest); + bidderRequestWithGDPR.gdprConsent = consent; + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + const data = request.data; + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.equal(consent.consentString); + }); + + describe('PriceFloors module support', function() { + const getFloorTest = (options) => { + switch (options.mediaType) { + case BANNER: + return { floor: 1, currency: 'USD' } + case VIDEO: + return { floor: 5, currency: 'USD' } + case NATIVE: + return { floor: 3, currency: 'USD' } + default: + return false + } + }; + + it('should not set `imp[]bidfloor` property when priceFloors module is not available', function () { + const bidRequests = [bid]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const data = request.data; + expect(data.imp[0].banner).to.exist; + expect(data.imp[0].bidfloor).to.not.exist + }); + + it('should not set `imp[]bidfloor` property when priceFloors module returns false', function () { + const bidWithPriceFloors = utils.deepClone(bid); + + bidWithPriceFloors.getFloor = () => { + return false; + }; + + const bidRequests = [bidWithPriceFloors]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const data = request.data; + + expect(data.imp[0].banner).to.exist; + expect(data.imp[0].bidfloor).to.not.exist; + }); + + it('should get the highest floorPrice found when bid have several mediaTypes', function() { + const bidWithPriceFloors = utils.deepClone(bid); + + bidWithPriceFloors.mediaTypes.video = { + playerSize: [600, 480] + }; + + bidWithPriceFloors.mediaTypes.native = { + body: { + required: true + } + }; + + bidWithPriceFloors.nativeParams = { + body: { + required: true + } + }; + + bidWithPriceFloors.getFloor = getFloorTest; + + const bidRequests = [bidWithPriceFloors]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const data = request.data; + + expect(data.imp[0].banner).to.exist; + expect(data.imp[0].video).to.exist; + expect(data.imp[0].native).to.exist; + expect(data.imp[0].bidfloor).to.equal(5); + }); + + it('should set properties at payload level from FPD', function() { + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + ortb2: { + site: { + domain: 'domain.example', + cat: ['IAB12'], + ext: { + data: { + category: 'sport', + } + } + }, + user: { + yob: 1985, + gender: 'm', + geo: { + country: 'FR', + city: 'Marseille' + }, + ext: { + data: { + registered: true + } + } + } + } + }; + return utils.deepAccess(config, key); + }); + + const bidRequests = [utils.deepClone(bid)]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const data = request.data; + expect(data.site.domain).to.equal('domain.example'); + expect(data.site.cat[0]).to.equal('IAB12'); + expect(data.site.ext.data.category).to.equal('sport'); + expect(data.user.yob).to.equal(1985); + expect(data.user.gender).to.equal('m'); + expect(data.user.geo.country).to.equal('FR'); + expect(data.user.geo.city).to.equal('Marseille'); + expect(data.user.ext.data.registered).to.be.true; + }); + }); + + describe('should support userId modules', function() { + const userId = { + pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', + unsuported: '666' + }; + + it('should send "user.eids" in the request for Prebid.js supported modules only', function() { + const bidCopy = utils.deepClone(bid); + bidCopy.userId = userId; + + const bidderRequestCopy = utils.deepClone(bidderRequest); + bidderRequestCopy.bids[0].userId = userId; + + const bidRequests = [utils.deepClone(bidCopy)]; + const request = spec.buildRequests(bidRequests, bidderRequestCopy); + const data = request.data; + + const expected = [{ + source: 'pubcid.org', + uids: [ + { + atype: 1, + id: '01EAJWWNEPN3CYMM5N8M5VXY22' + } + ] + }]; + expect(data.user.ext).to.exist; + expect(data.user.ext.eids).to.have.lengthOf(1); + expect(data.user.ext.eids).to.deep.equal(expected); + }); + }); + }); + + describe('intrepretResponse', function () { + const rawServerResponse = { + body: { + id: '60839f99-d5f2-3ab3-b6ac-736b4fe9d0ae', + seatbid: [ + { + bid: [ + { + id: '60839f99-d5f2-3ab3-b6ac-736b4fe9d0ae_0_0', + impid: '1', + price: 0.4319, + nurl: 'https://local.url/notif?index=ab-cd-ef&price=${AUCTION_PRICE}', + burl: 'https://local.url/notif?index=ab-cd-ef&price=${AUCTION_PRICE}', + adm: '', + adomain: ['domain.io'], + iurl: 'https://local.url', + cid: 'string-id', + crid: 'string-id', + cat: ['IAB2'], + attr: [], + w: 300, + h: 250, + ext: { + advertiser_name: 'Advertiser', + agency_name: 'mediakeys', + prebid: { + type: 'B' + } + }, + }, + ], + seat: '337', + }, + ], + cur: 'USD', + ext: { protocol: '5.3' }, + } + } + + it('Returns empty array if no bid', function () { + const bidRequests = [utils.deepClone(bid)]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const response01 = spec.interpretResponse({ body: { seatbid: [{ bid: [] }] } }, request); + const response02 = spec.interpretResponse({ body: { seatbid: [] } }, request); + const response03 = spec.interpretResponse({ body: { seatbid: null } }, request); + const response04 = spec.interpretResponse({ body: { seatbid: null } }, request); + const response05 = spec.interpretResponse({ body: {} }, request); + const response06 = spec.interpretResponse({}, request); + + expect(response01.length).to.equal(0); + expect(response02.length).to.equal(0); + expect(response03.length).to.equal(0); + expect(response04.length).to.equal(0); + expect(response05.length).to.equal(0); + expect(response06.length).to.equal(0); + }); + + it('Log an error', function () { + const bidRequests = [utils.deepClone(bid)]; + const request = spec.buildRequests(bidRequests, bidderRequest); + sinon.stub(utils, 'isArray').throws(); + utilsMock.expects('logError').once(); + spec.interpretResponse(rawServerResponse, request); + utils.isArray.restore(); + }); + + it('Meta Primary category handling', function() { + const rawServerResponseCopy = utils.deepClone(rawServerResponse); + const rawServerResponseCopy2 = utils.deepClone(rawServerResponse); + const rawServerResponseCopy3 = utils.deepClone(rawServerResponse); + const rawServerResponseCopy4 = utils.deepClone(rawServerResponse); + const rawServerResponseCopy5 = utils.deepClone(rawServerResponse); + const rawServerResponseCopy6 = utils.deepClone(rawServerResponse); + rawServerResponseCopy.body.seatbid[0].bid[0].cat = 'IAB12-1'; + rawServerResponseCopy2.body.seatbid[0].bid[0].cat = ['IAB12', 'IAB12-1']; + rawServerResponseCopy3.body.seatbid[0].bid[0].cat = ''; + rawServerResponseCopy4.body.seatbid[0].bid[0].cat = []; + rawServerResponseCopy5.body.seatbid[0].bid[0].cat = 123; + delete rawServerResponseCopy6.body.seatbid[0].bid[0].cat; + + const bidRequests = [utils.deepClone(bid)]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const response = spec.interpretResponse(rawServerResponseCopy, request); + const response2 = spec.interpretResponse(rawServerResponseCopy2, request); + const response3 = spec.interpretResponse(rawServerResponseCopy3, request); + const response4 = spec.interpretResponse(rawServerResponseCopy4, request); + const response5 = spec.interpretResponse(rawServerResponseCopy5, request); + const response6 = spec.interpretResponse(rawServerResponseCopy6, request); + expect(response[0].meta.primaryCatId).to.equal('IAB12-1'); + expect(response2[0].meta.primaryCatId).to.equal('IAB12'); + expect(response3[0].meta.primaryCatId).to.not.exist; + expect(response4[0].meta.primaryCatId).to.not.exist; + expect(response5[0].meta.primaryCatId).to.not.exist; + expect(response6[0].meta.primaryCatId).to.not.exist; + }); + + it('Build banner response', function () { + const bidRequests = [utils.deepClone(bid)]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const response = spec.interpretResponse(rawServerResponse, request); + + expect(response.length).to.equal(1); + expect(response[0].requestId).to.equal(rawServerResponse.body.seatbid[0].bid[0].impid); + expect(response[0].cpm).to.equal(rawServerResponse.body.seatbid[0].bid[0].price); + expect(response[0].width).to.equal(rawServerResponse.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(rawServerResponse.body.seatbid[0].bid[0].h); + expect(response[0].creativeId).to.equal(rawServerResponse.body.seatbid[0].bid[0].crid); + expect(response[0].dealId).to.equal(null); + expect(response[0].currency).to.equal(rawServerResponse.body.cur); + expect(response[0].netRevenue).to.equal(true); + expect(response[0].ttl).to.equal(360); + expect(response[0].referrer).to.equal(''); + expect(response[0].ad).to.equal(rawServerResponse.body.seatbid[0].bid[0].adm); + expect(response[0].mediaType).to.equal('banner'); + expect(response[0].burl).to.equal(rawServerResponse.body.seatbid[0].bid[0].burl); + expect(response[0].meta).to.deep.equal({ + advertiserDomains: rawServerResponse.body.seatbid[0].bid[0].adomain, + advertiserName: 'Advertiser', + agencyName: 'mediakeys', + primaryCatId: 'IAB2', + mediaType: 'banner' + }); + }); + + it('interprets video bid response', function () { + const vastUrl = 'https://url.local?req=content'; + const bidRequests = [utils.deepClone(bidVideo)]; + const request = spec.buildRequests(bidRequests, bidderRequest); + + const rawServerResponseVideo = utils.deepClone(rawServerResponse); + rawServerResponseVideo.body.seatbid[0].bid[0].ext.prebid.type = 'V'; + rawServerResponseVideo.body.seatbid[0].bid[0].ext.vast_url = vastUrl; + + const response = spec.interpretResponse(rawServerResponseVideo, request); + + expect(response.length).to.equal(1); + expect(response[0].mediaType).to.equal('video'); + expect(response[0].meta.mediaType).to.equal('video'); + expect(response[0].vastXml).to.not.exist; + expect(response[0].vastUrl).to.equal(vastUrl + '&no_cache'); + expect(response[0].videoCacheKey).to.equal('no_cache'); + }); + + describe('Native response', function () { + let bidRequests; + let bidderRequestCopy; + let request; + let rawServerResponseNative; + let nativeObject; + + beforeEach(function() { + bidRequests = [utils.deepClone(bidNative)]; + bidderRequestCopy = utils.deepClone(bidderRequest); + bidderRequestCopy.bids = bidRequests; + + request = spec.buildRequests(bidRequests, bidderRequestCopy); + + nativeObject = { + ver: '1.2', + privacy: 'https://privacy.me', + assets: [ + { id: 5, data: { type: 1, value: 'Sponsor Brand' } }, + { id: 6, data: { type: 2, value: 'Brand Body' } }, + { id: 14, data: { type: 10, value: 'Brand Body 2' } }, + { id: 1, title: { text: 'Brand Title' } }, + { id: 2, img: { type: 3, url: 'https://url.com/img.jpg', w: 300, h: 250 } }, + { id: 3, img: { type: 1, url: 'https://url.com/ico.png', w: 50, h: 50 } }, + ], + link: { + url: 'https://brand.me', + clicktrackers: [ + 'https://click.me' + ] + }, + eventtrackers: [ + { event: 1, method: 1, url: 'https://click.me' }, + { event: 1, method: 2, url: 'https://click-script.me' } + ] + }; + + rawServerResponseNative = utils.deepClone(rawServerResponse); + rawServerResponseNative.body.seatbid[0].bid[0].ext.prebid.type = 'N'; + rawServerResponseNative.body.seatbid[0].bid[0].adm = JSON.stringify(nativeObject) + }); + + it('should ignore invalid native response', function() { + const nativeObjectCopy = utils.deepClone(nativeObject); + nativeObjectCopy.assets = []; + const rawServerResponseNativeCopy = utils.deepClone(rawServerResponseNative); + rawServerResponseNativeCopy.body.seatbid[0].bid[0].adm = JSON.stringify(nativeObjectCopy) + const response = spec.interpretResponse(rawServerResponseNativeCopy, request); + expect(response.length).to.equal(1); + expect(response[0].native).to.not.exist; + }); + + it('should build a classic Prebid.js native object for response', function() { + const rawServerResponseNativeCopy = utils.deepClone(rawServerResponseNative); + const response = spec.interpretResponse(rawServerResponseNativeCopy, request); + expect(response.length).to.equal(1); + expect(response[0].mediaType).to.equal('native'); + expect(response[0].meta.mediaType).to.equal('native'); + expect(response[0].native).to.exist; + expect(response[0].native.body).to.exist; + expect(response[0].native.privacyLink).to.exist; + expect(response[0].native.body2).to.exist; + expect(response[0].native.sponsoredBy).to.exist; + expect(response[0].native.image).to.exist; + expect(response[0].native.icon).to.exist; + expect(response[0].native.title).to.exist; + expect(response[0].native.clickUrl).to.exist; + expect(response[0].native.clickTrackers).to.exist; + expect(response[0].native.clickTrackers.length).to.equal(1); + expect(response[0].native.javascriptTrackers).to.equal(''); + expect(response[0].native.impressionTrackers).to.exist; + expect(response[0].native.impressionTrackers.length).to.equal(1); + }); + + it('should ignore eventtrackers with a unsupported type', function() { + const rawServerResponseNativeCopy = utils.deepClone(rawServerResponseNative); + const nativeObjectCopy = utils.deepClone(nativeObject); + nativeObjectCopy.eventtrackers[0].event = 2; + rawServerResponseNativeCopy.body.seatbid[0].bid[0].adm = JSON.stringify(nativeObjectCopy); + const response = spec.interpretResponse(rawServerResponseNativeCopy, request); + expect(response[0].native.impressionTrackers).to.exist; + expect(response[0].native.impressionTrackers.length).to.equal(0); + }) + }); + }); + + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should not trigger pixel if bid does not contain burl', function() { + const result = spec.onBidWon({}); + expect(result).to.be.undefined; + expect(utils.triggerPixel.callCount).to.equal(0); + }) + + it('Should trigger pixel if bid.burl exists', function() { + const result = spec.onBidWon({ + cpm: 4.2, + burl: 'https://example.com/p=${AUCTION_PRICE}&foo=bar' + }); + + expect(utils.triggerPixel.callCount).to.equal(1) + expect(utils.triggerPixel.firstCall.args[0]).to.be.equal( + 'https://example.com/p=4.2&foo=bar' + ); + }) + }) +}); diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index 41a6338225e..a0a62710a56 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -11,10 +11,12 @@ const { const MOCK = { Ad_Units: [{'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'bids': [], 'ext': {'prop1': 'value1'}}], MULTI_FORMAT_TWIN_AD_UNITS: [{'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250]]}, 'native': {'image': {'required': true, 'sizes': [150, 50]}}}, 'bids': [], 'ext': {'prop1': 'value1'}}, {'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'video': {'playerSize': [640, 480], 'context': 'instream'}}, 'bids': [], 'ext': {'prop1': 'value1'}}], + TWIN_AD_UNITS: [{'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 100]]}}, 'ask': '300x100', 'bids': [{'bidder': 'bidder1', 'params': {'siteId': '451465'}}]}, {'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250], [300, 100]]}}, 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}}]}, {'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'bids': [{'bidder': 'bidder1', 'params': {'siteId': '451466'}}]}], AUCTION_INIT: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739, 'timeout': 6000}, AUCTION_INIT_WITH_FLOOR: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739, 'timeout': 6000, 'bidderRequests': [{'bids': [{ 'floorData': {'enforcements': {'enforceJS': true}} }]}]}, BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, MULTI_FORMAT_BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}, 'video': {'playerSize': [640, 480], 'context': 'instream'}, 'native': {'image': {'required': true, 'sizes': [150, 50]}, 'title': {'required': true, 'len': 80}}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, + TWIN_AD_UNITS_BID_REQUESTED: [{'bidderCode': 'bidder1', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bidderRequestId': '16f0746ff657b5', 'bids': [{'bidder': 'bidder1', 'params': {'siteId': '451465'}, 'mediaTypes': {'banner': {'sizes': [[300, 100]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '9615b5d1-7a4f-4c65-9464-4178b91da9e3', 'sizes': [[300, 100]], 'bidId': '2984d18e18bdfe', 'bidderRequestId': '16f0746ff657b5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client', 'bidRequestsCount': 3, 'bidderRequestsCount': 2, 'bidderWinsCount': 0}, {'bidder': 'bidder1', 'params': {'siteId': '451466'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '8bd7c9f2-0fe6-4ac5-8f2a-7f4a88af1b71', 'sizes': [[300, 250]], 'bidId': '3dced609066035', 'bidderRequestId': '16f0746ff657b5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client', 'bidRequestsCount': 3, 'bidderRequestsCount': 2, 'bidderWinsCount': 0}], 'auctionStart': 1584563605739, 'timeout': 3000, 'start': 1584563605743}, {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bidderRequestId': '4b45d1de1fa8fe', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250], [300, 100]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '215c038e-3b6a-465b-8937-d32e2ad8de45', 'sizes': [[300, 250], [300, 100]], 'bidId': '58d34adcb09c99', 'bidderRequestId': '4b45d1de1fa8fe', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client', 'bidRequestsCount': 3, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1584563605739, 'timeout': 3000, 'start': 1584563605743}], BID_RESPONSE: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, AUCTION_END: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'auctionEnd': 1584563605739}, SET_TARGETING: {'div-gpt-ad-1460505748561-0': {'prebid_test': '1', 'hb_format': 'banner', 'hb_source': 'client', 'hb_size': '300x250', 'hb_pb': '2.00', 'hb_adid': '3e6e4bce5c8fb3', 'hb_bidder': 'medianet', 'hb_format_medianet': 'banner', 'hb_source_medianet': 'client', 'hb_size_medianet': '300x250', 'hb_pb_medianet': '2.00', 'hb_adid_medianet': '3e6e4bce5c8fb3', 'hb_bidder_medianet': 'medianet'}}, @@ -25,7 +27,7 @@ const MOCK = { } function performAuctionWithFloorConfig() { - events.emit(AUCTION_INIT, {...MOCK.AUCTION_INIT_WITH_FLOOR, adUnits: MOCK.Ad_Units}); + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT_WITH_FLOOR, {adUnits: MOCK.Ad_Units})); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); events.emit(AUCTION_END, MOCK.AUCTION_END); @@ -34,7 +36,7 @@ function performAuctionWithFloorConfig() { } function performStandardAuctionWithWinner() { - events.emit(AUCTION_INIT, {...MOCK.AUCTION_INIT, adUnits: MOCK.Ad_Units}); + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); events.emit(AUCTION_END, MOCK.AUCTION_END); @@ -43,15 +45,23 @@ function performStandardAuctionWithWinner() { } function performMultiFormatAuctionWithNoBid() { - events.emit(AUCTION_INIT, {...MOCK.AUCTION_INIT, adUnits: MOCK.MULTI_FORMAT_TWIN_AD_UNITS}); + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.MULTI_FORMAT_TWIN_AD_UNITS})); events.emit(BID_REQUESTED, MOCK.MULTI_FORMAT_BID_REQUESTED); events.emit(NO_BID, MOCK.NO_BID); events.emit(AUCTION_END, MOCK.AUCTION_END); events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); } +function performTwinAdUnitAuctionWithNoBid() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.TWIN_AD_UNITS})); + MOCK.TWIN_AD_UNITS_BID_REQUESTED.forEach(bidRequested => events.emit(BID_REQUESTED, bidRequested)); + MOCK.TWIN_AD_UNITS_BID_REQUESTED.forEach(bidRequested => bidRequested.bids.forEach(noBid => events.emit(NO_BID, noBid))); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); +} + function performStandardAuctionWithNoBid() { - events.emit(AUCTION_INIT, {...MOCK.AUCTION_INIT, adUnits: MOCK.Ad_Units}); + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(NO_BID, MOCK.NO_BID); events.emit(AUCTION_END, MOCK.AUCTION_END); @@ -59,17 +69,20 @@ function performStandardAuctionWithNoBid() { } function performStandardAuctionWithTimeout() { - events.emit(AUCTION_INIT, {...MOCK.AUCTION_INIT, adUnits: MOCK.Ad_Units}); + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); events.emit(AUCTION_END, MOCK.AUCTION_END); events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); } -function getQueryData(url) { +function getQueryData(url, decode = false) { const queryArgs = url.split('?')[1].split('&'); return queryArgs.reduce((data, arg) => { - const [key, val] = arg.split('='); + let [key, val] = arg.split('='); + if (decode) { + val = decodeURIComponent(val); + } if (data[key] !== undefined) { if (!Array.isArray(data[key])) { data[key] = [data[key]]; @@ -149,6 +162,25 @@ describe('Media.net Analytics Adapter', function() { expect(noBidLog.vplcmtt).to.equal('instream'); }); + it('twin ad units should have correct sizes', function() { + performTwinAdUnitAuctionWithNoBid(); + const noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log, true))[0]; + const banner = 'banner'; + expect(noBidLog.pvnm).to.have.ordered.members(['-2', 'bidder1', 'bidder1', 'medianet']); + expect(noBidLog.mtype).to.have.ordered.members([banner, banner, banner, banner]); + expect(noBidLog.status).to.have.ordered.members(['1', '2', '2', '2']); + expect(noBidLog.size).to.have.ordered.members(['', '', '', '']); + expect(noBidLog.szs).to.have.ordered.members(['300x100|300x250', '300x100', '300x250', '300x250|300x100']); + }); + + it('AP log should fire only once', function() { + performStandardAuctionWithNoBid(); + events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); + const logs = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log, true)); + expect(logs.length).to.equal(1); + expect(logs[0].lgtp).to.equal('APPR'); + }); + it('should have winner log in standard auction', function() { medianetAnalytics.clearlogsQueue(); performStandardAuctionWithWinner(); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 1eeb167601e..8589c3b404f 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -1,4 +1,4 @@ -import {expect} from 'chai'; +import {expect, assert} from 'chai'; import {spec} from 'modules/medianetBidAdapter.js'; import { makeSlot } from '../integration/faker/googletag.js'; import { config } from 'src/config.js'; @@ -99,6 +99,56 @@ let VALID_BID_REQUEST = [{ 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'bidRequestsCount': 1 }], + VALID_BID_REQUEST_WITH_ORTB2 = [{ + 'bidder': 'medianet', + 'params': { + 'crid': 'crid', + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]], + } + }, + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'ortb2Imp': { 'ext': { 'data': { 'pbadslot': '/12345/my-gpt-tag-0' } } }, + 'bidRequestsCount': 1 + }, { + 'bidder': 'medianet', + 'params': { + 'crid': 'crid', + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, + 'sizes': [[300, 251]], + 'bidId': '3f97ca71b1e5c2', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'ortb2Imp': { 'ext': { 'data': { 'pbadslot': '/12345/my-gpt-tag-0' } } }, + 'bidRequestsCount': 1 + }], VALID_BID_REQUEST_WITH_USERID = [{ 'bidder': 'medianet', 'params': { @@ -1247,6 +1297,17 @@ describe('Media.net bid adapter', function () { expect(JSON.parse(bidreq.data)).to.deep.equal(VALID_PAYLOAD_WITH_CRID); }); + it('should have valid ortb2Imp param present in bid request', function() { + let bidreq = spec.buildRequests(VALID_BID_REQUEST_WITH_ORTB2, VALID_AUCTIONDATA); + let actual = JSON.parse(bidreq.data).imp[0].ortb2Imp; + const expected = VALID_BID_REQUEST_WITH_ORTB2[0].ortb2Imp + assert.equal(JSON.stringify(actual), JSON.stringify(expected)) + + bidreq = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA); + actual = JSON.parse(bidreq.data).imp[0].ortb2Imp; + assert.equal(actual, undefined) + }); + it('should have userid in bid request', function () { let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_USERID, VALID_AUCTIONDATA); expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_WITH_USERID); @@ -1446,4 +1507,45 @@ describe('Media.net bid adapter', function () { let bids = spec.interpretResponse(SERVER_VIDEO_OUTSTREAM_RESPONSE_VALID_BID, []); expect(bids[0].context).to.equal('outstream'); }); + describe('buildRequests floor tests', function () { + let floor; + let getFloor = function(req) { + return floor[req.mediaType]; + }; + beforeEach(function () { + floor = { + 'banner': { + 'currency': 'USD', + 'floor': 1 + } + }; + $$PREBID_GLOBAL$$.medianetGlobals = {}; + + let documentStub = sandbox.stub(document, 'getElementById'); + let boundingRect = { + top: 50, + left: 50, + bottom: 100, + right: 100 + }; + documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({ + getBoundingClientRect: () => boundingRect + }); + documentStub.withArgs('div-gpt-ad-1460505748561-0').returns({ + getBoundingClientRect: () => boundingRect + }); + let windowSizeStub = sandbox.stub(spec, 'getWindowSize'); + windowSizeStub.returns({ + w: 1000, + h: 1000 + }); + VALID_BID_REQUEST[0].getFloor = getFloor; + }); + + it('should build valid payload with floor', function () { + let requestObj = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA); + requestObj = JSON.parse(requestObj.data); + expect(requestObj.imp[0].hasOwnProperty('bidfloors')).to.equal(true); + }); + }); }); diff --git a/test/spec/modules/medianetRtdProvider_spec.js b/test/spec/modules/medianetRtdProvider_spec.js new file mode 100644 index 00000000000..7d73ecd5d44 --- /dev/null +++ b/test/spec/modules/medianetRtdProvider_spec.js @@ -0,0 +1,146 @@ +import * as medianetRTD from '../../../modules/medianetRtdProvider.js'; +import * as sinon from 'sinon'; +import { assert } from 'chai'; + +let sandbox; +let setDataSpy; +let getTargetingDataSpy; +let onPrebidRequestBidSpy; + +const conf = { + dataProviders: [{ + 'name': 'medianet', + 'params': { + 'cid': 'customer_id', + } + }] +}; + +describe('medianet realtime module', function () { + beforeEach(function () { + sandbox = sinon.sandbox.create(); + window.mnjs = window.mnjs || {}; + window.mnjs.que = window.mnjs.que || []; + window.mnjs.setData = setDataSpy = sandbox.spy(); + window.mnjs.getTargetingData = getTargetingDataSpy = sandbox.spy(); + window.mnjs.onPrebidRequestBid = onPrebidRequestBidSpy = sandbox.spy(); + }); + + afterEach(function () { + sandbox.restore(); + window.mnjs = {}; + }); + + it('init should return false when customer id is passed', function () { + assert.equal(medianetRTD.medianetRtdModule.init({}), false); + }); + + it('init should return true when customer id is passed', function () { + assert.equal(medianetRTD.medianetRtdModule.init(conf.dataProviders[0]), true); + }); + + it('init should pass config to js when loaded', function () { + medianetRTD.medianetRtdModule.init(conf.dataProviders[0]); + + const command = window.mnjs.que.pop(); + assert.isFunction(command); + command(); + + assert.equal(setDataSpy.called, true); + assert.equal(setDataSpy.args[0][0].name, 'initIRefresh'); + }); + + it('auctionInit should pass information to js when loaded', function () { + const auctionObject = {adUnits: []}; + medianetRTD.medianetRtdModule.onAuctionInitEvent(auctionObject); + + const command = window.mnjs.que.pop(); + assert.isFunction(command); + command(); + + assert.equal(setDataSpy.called, true); + assert.equal(setDataSpy.args[0][0].name, 'auctionInit'); + assert.deepEqual(setDataSpy.args[0][0].data, {auction: auctionObject}); + }); + + describe('getTargeting should work correctly', function () { + it('should return empty if not loaded', function () { + window.mnjs.loaded = false; + assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData([]), {}); + }); + + it('should return ad unit codes when ad units are present', function () { + const adUnitCodes = ['code1', 'code2']; + assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData(adUnitCodes), { + code1: {'mnadc': 'code1'}, + code2: {'mnadc': 'code2'}, + }); + }); + + it('should call mnjs.getTargetingData if loaded', function () { + window.mnjs.loaded = true; + medianetRTD.medianetRtdModule.getTargetingData([]); + assert.equal(getTargetingDataSpy.called, true); + }); + }); + + describe('getBidRequestData should work correctly', function () { + it('callback should be called when we are not interested in request', function () { + const requestBidsProps = { + adUnits: [{ + code: 'code1', bids: [], + }], + adUnitCodes: ['code1'], + }; + const callbackSpy = sandbox.spy(); + medianetRTD.medianetRtdModule.getBidRequestData(requestBidsProps, callbackSpy, conf.dataProviders[0], {}); + + const command = window.mnjs.que.pop(); + assert.isFunction(command); + command(); + + assert.equal(onPrebidRequestBidSpy.called, true, 'onPrebidRequest should always be called'); + assert.equal(callbackSpy.called, true, 'when onPrebidRequest returns nothing callback should be called immediately'); + }); + + it('we should wait for callback till onComplete', function () { + const requestBidsProps = { + adUnits: [{ + code: 'code1', bids: [], + }], + adUnitCodes: ['code1'], + }; + + const refreshInformation = { + mnrf: '1', + mnrfc: 2, + }; + + const callbackSpy = sandbox.spy(); + const onCompleteSpy = sandbox.spy(); + window.mnjs.onPrebidRequestBid = onPrebidRequestBidSpy = () => { + onPrebidRequestBidSpy.called = true; + return {onComplete: onCompleteSpy}; + }; + medianetRTD.medianetRtdModule.getBidRequestData(requestBidsProps, callbackSpy, conf.dataProviders[0], {}); + + const command = window.mnjs.que.pop(); + assert.isFunction(command); + command(); + + assert.equal(callbackSpy.called, false, 'callback should not be called, as we are returning a request from onPrebidRequestBid'); + assert.equal(onPrebidRequestBidSpy.called, true, 'onPrebidRequestBid should be called once'); + assert.equal(onCompleteSpy.called, true, 'onComplete should be passed callback'); + assert.isFunction(onCompleteSpy.args[0][0], 'onCompleteSpy first argument error callback should be a function'); + assert.isFunction(onCompleteSpy.args[0][1], 'onCompleteSpy second argument success callback should be a function'); + onCompleteSpy.args[0][0](); + assert.equal(callbackSpy.callCount, 1, 'callback should be called when error callback is triggered'); + onCompleteSpy.args[0][1]({}, { + 'code1': {ext: {refresh: refreshInformation}} + }); + assert.equal(callbackSpy.callCount, 2, 'callback should be called when success callback is triggered'); + assert.isObject(requestBidsProps.adUnits[0].ortb2Imp, 'ORTB object should be set'); + assert.deepEqual(requestBidsProps.adUnits[0].ortb2Imp.ext.refresh, refreshInformation, 'ORTB should have refresh information should be set'); + }); + }); +}); diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index 796be3420e8..86ab6e1f611 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -61,7 +61,25 @@ describe('MediaSquare bid adapter tests', function () { code: 'publishername_atf_desktop_rg_pave' }, }]; - + var FLOORS_PARAMS = [{ + adUnitCode: 'banner-div', + bidId: 'aaaa1234', + auctionId: 'bbbb1234', + transactionId: 'cccc1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bidder: 'mediasquare', + params: { + owner: 'test', + code: 'publishername_atf_desktop_rg_pave' + }, + getFloor: function (a) { return { currency: 'EUR', floor: 1.0 }; }, + }]; var BID_RESPONSE = {'body': { 'responses': [{ 'transaction_id': 'cccc1234', @@ -76,6 +94,7 @@ describe('MediaSquare bid adapter tests', function () { 'bidder': 'msqClassic', 'code': 'test/publishername_atf_desktop_rg_pave', 'bid_id': 'aaaa1234', + 'adomain': ['test.com'], }], }}; @@ -116,6 +135,12 @@ describe('MediaSquare bid adapter tests', function () { expect(requestContent.codes[0]).to.have.property('auctionId').and.to.equal('bbbb1234'); expect(requestContent.codes[0]).to.have.property('transactionId').and.to.equal('cccc1234'); expect(requestContent.codes[0]).to.have.property('mediatypes').exist; + expect(requestContent.codes[0]).to.have.property('floor').exist; + expect(requestContent.codes[0].floor).to.deep.equal({}); + const requestfloor = spec.buildRequests(FLOORS_PARAMS, DEFAULT_OPTIONS); + const responsefloor = JSON.parse(requestfloor.data); + expect(responsefloor.codes[0]).to.have.property('floor').exist; + expect(responsefloor.codes[0].floor).to.have.property('floor').and.to.equal(1.0); }); it('Verify parse response', function () { @@ -135,8 +160,18 @@ describe('MediaSquare bid adapter tests', function () { expect(bid.mediasquare).to.exist; expect(bid.mediasquare.bidder).to.equal('msqClassic'); expect(bid.mediasquare.code).to.equal([DEFAULT_PARAMS[0].params.owner, DEFAULT_PARAMS[0].params.code].join('/')); + expect(bid.meta).to.exist; + expect(bid.meta.advertiserDomains).to.exist; + expect(bid.meta.advertiserDomains).to.have.lengthOf(1); + }); + it('Verifies match', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + BID_RESPONSE.body.responses[0].match = true; + const response = spec.interpretResponse(BID_RESPONSE, request); + const bid = response[0]; + expect(bid.mediasquare.match).to.exist; + expect(bid.mediasquare.match).to.equal(true); }); - it('Verifies bidder code', function () { expect(spec.code).to.equal('mediasquare'); }); @@ -156,7 +191,7 @@ describe('MediaSquare bid adapter tests', function () { }); it('Verifies user sync without cookie in bid response', function () { var syncs = spec.getUserSyncs({}, [BID_RESPONSE], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.property('type').and.to.equal('iframe'); + expect(syncs).to.have.lengthOf(0); }); it('Verifies user sync with cookies in bid response', function () { BID_RESPONSE.body.cookies = [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}]; @@ -167,13 +202,13 @@ describe('MediaSquare bid adapter tests', function () { }); it('Verifies user sync with no bid response', function() { var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.property('type').and.to.equal('iframe'); + expect(syncs).to.have.lengthOf(0); }); it('Verifies user sync with no bid body response', function() { var syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.property('type').and.to.equal('iframe'); + expect(syncs).to.have.lengthOf(0); var syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.property('type').and.to.equal('iframe'); + expect(syncs).to.have.lengthOf(0); }); it('Verifies native in bid response', function () { const request = spec.buildRequests(NATIVE_PARAMS, DEFAULT_OPTIONS); diff --git a/test/spec/modules/merkleIdSystem_spec.js b/test/spec/modules/merkleIdSystem_spec.js new file mode 100644 index 00000000000..63a2791ba3c --- /dev/null +++ b/test/spec/modules/merkleIdSystem_spec.js @@ -0,0 +1,230 @@ +import * as ajaxLib from 'src/ajax.js'; +import * as utils from 'src/utils.js'; +import {merkleIdSubmodule} from 'modules/merkleIdSystem.js'; + +import sinon from 'sinon'; + +let expect = require('chai').expect; + +const CONFIG_PARAMS = { + endpoint: 'https://test/id', + vendor: 'idsv2', + sv_cid: '5344_04531', + sv_pubid: '11314', + sv_domain: 'www.testDomain.com', + sv_session: 'testsession' +}; + +const STORAGE_PARAMS = { + type: 'cookie', + name: 'merkle', + expires: 10, + refreshInSeconds: 10 +}; + +const MOCK_RESPONSE = { + c: { + name: '_svsid', + value: '123876327647627364236478' + } +}; + +function mockResponse( + responseText, + response = (url, successCallback) => successCallback(responseText)) { + return function() { + return response; + } +} + +describe('Merkle System', function () { + describe('Merkle System getId()', function () { + const callbackSpy = sinon.spy(); + let sandbox; + let ajaxStub; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + sinon.stub(utils, 'logInfo'); + sinon.stub(utils, 'logWarn'); + sinon.stub(utils, 'logError'); + callbackSpy.resetHistory(); + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockResponse(JSON.stringify(MOCK_RESPONSE))); + }); + + afterEach(function () { + utils.logInfo.restore(); + utils.logWarn.restore(); + utils.logError.restore(); + ajaxStub.restore(); + }); + + it('getId() should fail on missing vendor', function () { + let config = { + params: { + ...CONFIG_PARAMS, + vendor: undefined + }, + storage: STORAGE_PARAMS + }; + + let submoduleCallback = merkleIdSubmodule.getId(config, undefined); + expect(submoduleCallback).to.be.undefined; + expect(utils.logError.args[0][0]).to.exist.and.to.equal('User ID - merkleId submodule requires a valid vendor to be defined'); + }); + + it('getId() should fail on missing vendor', function () { + let config = { + params: { + ...CONFIG_PARAMS, + vendor: undefined + }, + storage: STORAGE_PARAMS + }; + + let submoduleCallback = merkleIdSubmodule.getId(config, undefined); + expect(submoduleCallback).to.be.undefined; + expect(utils.logError.args[0][0]).to.exist.and.to.equal('User ID - merkleId submodule requires a valid vendor to be defined'); + }); + + it('getId() should fail on missing sv_cid', function () { + let config = { + params: { + ...CONFIG_PARAMS, + sv_cid: undefined + }, + storage: STORAGE_PARAMS + }; + + let submoduleCallback = merkleIdSubmodule.getId(config, undefined); + expect(submoduleCallback).to.be.undefined; + expect(utils.logError.args[0][0]).to.exist.and.to.equal('User ID - merkleId submodule requires a valid sv_cid string to be defined'); + }); + + it('getId() should fail on missing sv_pubid', function () { + let config = { + params: { + ...CONFIG_PARAMS, + sv_pubid: undefined + }, + storage: STORAGE_PARAMS + }; + + let submoduleCallback = merkleIdSubmodule.getId(config, undefined); + expect(submoduleCallback).to.be.undefined; + expect(utils.logError.args[0][0]).to.exist.and.to.equal('User ID - merkleId submodule requires a valid sv_pubid string to be defined'); + }); + + it('getId() should warn on missing endpoint', function () { + let config = { + params: { + ...CONFIG_PARAMS, + endpoint: undefined + }, + storage: STORAGE_PARAMS + }; + + let submoduleCallback = merkleIdSubmodule.getId(config, undefined).callback; + submoduleCallback(callbackSpy); + expect(callbackSpy.calledOnce).to.be.true; + expect(utils.logWarn.args[0][0]).to.exist.and.to.equal('User ID - merkleId submodule endpoint string is not defined'); + }); + + it('getId() should handle callback with valid configuration', function () { + let config = { + params: CONFIG_PARAMS, + storage: STORAGE_PARAMS + }; + + let submoduleCallback = merkleIdSubmodule.getId(config, undefined).callback; + submoduleCallback(callbackSpy); + expect(callbackSpy.calledOnce).to.be.true; + }); + }); + + describe('Merkle System extendId()', function () { + const callbackSpy = sinon.spy(); + let sandbox; + let ajaxStub; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + sinon.stub(utils, 'logInfo'); + sinon.stub(utils, 'logWarn'); + sinon.stub(utils, 'logError'); + callbackSpy.resetHistory(); + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockResponse(JSON.stringify(MOCK_RESPONSE))); + }); + + afterEach(function () { + utils.logInfo.restore(); + utils.logWarn.restore(); + utils.logError.restore(); + ajaxStub.restore(); + }); + + it('extendId() get storedid', function () { + let config = { + params: { + ...CONFIG_PARAMS, + }, + storage: STORAGE_PARAMS + }; + + let id = merkleIdSubmodule.extendId(config, undefined, 'Merkle_Stored_ID'); + expect(id.id).to.exist.and.to.equal('Merkle_Stored_ID'); + }); + + it('extendId() get storedId on configured storageParam.refreshInSeconds', function () { + let config = { + params: { + ...CONFIG_PARAMS, + refreshInSeconds: 1000 + }, + storage: STORAGE_PARAMS + }; + + let yesterday = new Date(Date.now() - 86400000).toUTCString(); + let storedId = {value: 'Merkle_Stored_ID', date: yesterday}; + + let id = merkleIdSubmodule.extendId(config, undefined, + storedId); + + expect(id.id).to.exist.and.to.equal(storedId); + }); + it('extendId() should warn on missing endpoint', function () { + let config = { + params: { + ...CONFIG_PARAMS, + endpoint: undefined + }, + storage: STORAGE_PARAMS + }; + + let yesterday = new Date(Date.now() - 86400000).toUTCString(); + let storedId = {value: 'Merkle_Stored_ID', date: yesterday}; + + let submoduleCallback = merkleIdSubmodule.extendId(config, undefined, + storedId).callback; + submoduleCallback(callbackSpy); + expect(callbackSpy.calledOnce).to.be.true; + expect(utils.logWarn.args[0][0]).to.exist.and.to.equal('User ID - merkleId submodule endpoint string is not defined'); + }); + + it('extendId() callback on configured storageParam.refreshInSeconds', function () { + let config = { + params: { + ...CONFIG_PARAMS, + refreshInSeconds: 1 + } + }; + + let yesterday = new Date(Date.now() - 86400000).toUTCString(); + let storedId = {value: 'Merkle_Stored_ID', date: yesterday}; + + let submoduleCallback = merkleIdSubmodule.extendId(config, undefined, storedId).callback; + submoduleCallback(callbackSpy); + expect(callbackSpy.calledOnce).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/mgidBidAdapter_spec.js b/test/spec/modules/mgidBidAdapter_spec.js index 16f4f0b4607..34ad29b3e92 100644 --- a/test/spec/modules/mgidBidAdapter_spec.js +++ b/test/spec/modules/mgidBidAdapter_spec.js @@ -1,5 +1,6 @@ import {assert, expect} from 'chai'; -import {spec} from 'modules/mgidBidAdapter.js'; +import { spec, storage } from 'modules/mgidBidAdapter.js'; +import { version } from 'package.json'; import * as utils from '../../../src/utils.js'; describe('Mgid bid adapter', function () { @@ -28,7 +29,6 @@ describe('Mgid bid adapter', function () { } const secure = window.location.protocol === 'https:' ? 1 : 0; const mgid_ver = spec.VERSION; - const prebid_ver = $$PREBID_GLOBAL$$.version; const utcOffset = (new Date()).getTimezoneOffset().toString(); describe('isBidRequestValid', function () { @@ -311,10 +311,6 @@ describe('Mgid bid adapter', function () { }); describe('buildRequests', function () { - it('should return undefined if no validBidRequests passed', function () { - expect(spec.buildRequests([])).to.be.undefined; - }); - let abid = { adUnitCode: 'div', bidder: 'mgid', @@ -323,8 +319,14 @@ describe('Mgid bid adapter', function () { placementId: '2', }, }; - it('should return proper request url', function () { - localStorage.setItem('mgMuidn', 'xxx'); + + it('should return undefined if no validBidRequests passed', function () { + expect(spec.buildRequests([])).to.be.undefined; + }); + it('should return request url with muid', function () { + let getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + getDataFromLocalStorageStub.withArgs('mgMuidn').returns('xxx'); + let bid = Object.assign({}, abid); bid.mediaTypes = { banner: { @@ -334,7 +336,8 @@ describe('Mgid bid adapter', function () { let bidRequests = [bid]; const request = spec.buildRequests(bidRequests); expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1?muid=xxx'); - localStorage.removeItem('mgMuidn') + + getDataFromLocalStorageStub.restore(); }); it('should proper handle gdpr', function () { let bid = Object.assign({}, abid); @@ -379,7 +382,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{\"site\":{\"domain\":\"' + domain + '\",\"page\":\"' + page + '\"},\"cur\":[\"USD\"],\"geo\":{\"utcoffset\":' + utcOffset + '},\"device\":{\"ua\":\"' + ua + '\",\"js\":1,\"dnt\":' + dnt + ',\"h\":' + screenHeight + ',\"w\":' + screenWidth + ',\"language\":\"' + lang + '\"},\"ext\":{\"mgid_ver\":\"' + mgid_ver + '\",\"prebid_ver\":\"' + prebid_ver + '\"},\"imp\":[{\"tagid\":\"2/div\",\"secure\":' + secure + ',\"banner\":{\"w\":300,\"h\":250}}]}', + 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"banner":{"w":300,"h":250}}]}', }); }); it('should not return native imp if minimum asset list not requested', function () { @@ -428,10 +431,10 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{\"site\":{\"domain\":\"' + domain + '\",\"page\":\"' + page + '\"},\"cur\":[\"USD\"],\"geo\":{\"utcoffset\":' + utcOffset + '},\"device\":{\"ua\":\"' + ua + '\",\"js\":1,\"dnt\":' + dnt + ',\"h\":' + screenHeight + ',\"w\":' + screenWidth + ',\"language\":\"' + lang + '\"},\"ext\":{\"mgid_ver\":\"' + mgid_ver + '\",\"prebid_ver\":\"' + prebid_ver + '\"},\"imp\":[{\"tagid\":\"2/div\",\"secure\":' + secure + ',\"native\":{\"request\":{\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":80}},{\"id\":2,\"required\":0,\"img\":{\"type\":3,\"w\":80,\"h\":80}},{\"id\":11,\"required\":0,\"data\":{\"type\":1}}]}}}]}', + 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":11,"required":0,"data":{"type":1}}]}}}]}', }); }); - it('should return proper native imp', function () { + it('should return proper native imp with image altered', function () { let bid = Object.assign({}, abid); bid.mediaTypes = { native: '', @@ -465,7 +468,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{\"site\":{\"domain\":\"' + domain + '\",\"page\":\"' + page + '\"},\"cur\":[\"USD\"],\"geo\":{\"utcoffset\":' + utcOffset + '},\"device\":{\"ua\":\"' + ua + '\",\"js\":1,\"dnt\":' + dnt + ',\"h\":' + screenHeight + ',\"w\":' + screenWidth + ',\"language\":\"' + lang + '\"},\"ext\":{\"mgid_ver\":\"' + mgid_ver + '\",\"prebid_ver\":\"' + prebid_ver + '\"},\"imp\":[{\"tagid\":\"2/div\",\"secure\":' + secure + ',\"native\":{\"request\":{\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":80}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"w\":492,\"h\":328,\"wmin\":50,\"hmin\":50}},{\"id\":3,\"required\":0,\"img\":{\"type\":1,\"w\":50,\"h\":50}},{\"id\":11,\"required\":0,\"data\":{\"type\":1}}]}}}]}', + 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":1,"img":{"type":3,"w":492,"h":328,"wmin":50,"hmin":50}},{"id":3,"required":0,"img":{"type":1,"w":50,"h":50}},{"id":11,"required":0,"data":{"type":1}}]}}}]}', }); }); it('should return proper native imp with sponsoredBy', function () { @@ -501,7 +504,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{\"site\":{\"domain\":\"' + domain + '\",\"page\":\"' + page + '\"},\"cur\":[\"USD\"],\"geo\":{\"utcoffset\":' + utcOffset + '},\"device\":{\"ua\":\"' + ua + '\",\"js\":1,\"dnt\":' + dnt + ',\"h\":' + screenHeight + ',\"w\":' + screenWidth + ',\"language\":\"' + lang + '\"},\"ext\":{\"mgid_ver\":\"' + mgid_ver + '\",\"prebid_ver\":\"' + prebid_ver + '\"},\"imp\":[{\"tagid\":\"2/div\",\"secure\":' + secure + ',\"native\":{\"request\":{\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":80}},{\"id\":2,\"required\":0,\"img\":{\"type\":3,\"w\":80,\"h\":80}},{\"id\":4,\"required\":0,\"data\":{\"type\":1}}]}}}]}', + 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":4,"required":0,"data":{"type":1}}]}}}]}', }); }); it('should return proper banner request', function () { @@ -509,7 +512,8 @@ describe('Mgid bid adapter', function () { bid.mediaTypes = { banner: { sizes: [[300, 600], [300, 250]], - } + pos: 1, + }, }; let bidRequests = [bid]; const request = spec.buildRequests(bidRequests); @@ -528,68 +532,39 @@ describe('Mgid bid adapter', function () { expect(data.device.w).equal(screenWidth); expect(data.device.language).to.deep.equal(lang); expect(data.imp[0].tagid).to.deep.equal('2/div'); - expect(data.imp[0].banner).to.deep.equal({w: 300, h: 600, format: [{w: 300, h: 600}, {w: 300, h: 250}]}); + expect(data.imp[0].banner).to.deep.equal({w: 300, h: 600, format: [{w: 300, h: 600}, {w: 300, h: 250}], pos: 1}); expect(data.imp[0].secure).to.deep.equal(secure); expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{\"site\":{\"domain\":\"' + domain + '\",\"page\":\"' + page + '\"},\"cur\":[\"USD\"],\"geo\":{\"utcoffset\":' + utcOffset + '},\"device\":{\"ua\":\"' + ua + '\",\"js\":1,\"dnt\":' + dnt + ',\"h\":' + screenHeight + ',\"w\":' + screenWidth + ',\"language\":\"' + lang + '\"},\"ext\":{\"mgid_ver\":\"' + mgid_ver + '\",\"prebid_ver\":\"' + prebid_ver + '\"},\"imp\":[{\"tagid\":\"2/div\",\"secure\":' + secure + ',\"banner\":{\"w\":300,\"h\":600,\"format\":[{\"w\":300,\"h\":600},{\"w\":300,\"h\":250}]}}]}', + 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"banner":{"w":300,"h":600,"format":[{"w":300,"h":600},{"w":300,"h":250}],"pos":1}}]}', }); }); }); - describe('interpretResponse banner', function () { - it('should not push bid response', function () { - let bids = spec.interpretResponse(); - expect(bids).to.be.undefined; - }); - it('should push proper banner bid response', function () { - let resp = { - body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': '', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'https nurl', 'burl': 'https burl', 'adm': 'html: adm', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2']}], 'seat': '44082'}]} - }; - let bids = spec.interpretResponse(resp); - expect(bids).to.deep.equal([ - { - 'ad': 'html: adm', - 'cpm': 1.5, - 'creativeId': '2898532/2419121/2592854/2499195', - 'currency': 'USD', - 'dealId': '', - 'height': 600, - 'isBurl': true, - 'mediaType': 'banner', - 'netRevenue': true, - 'nurl': 'https nurl', - 'burl': 'https burl', - 'requestId': '61e40632c53fc2', - 'ttl': 300, - 'width': 300, - } - ]); - }); - }); - describe('interpretResponse native', function () { + + describe('interpretResponse', function () { it('should not push proper native bid response if adm is missing', function () { let resp = { - body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'https nurl', 'burl': 'https burl', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}}], 'seat': '44082'}]} + body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'https nurl', 'burl': 'https burl', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}, 'adomain': ['test.com']}], 'seat': '44082'}]} }; let bids = spec.interpretResponse(resp); expect(bids).to.deep.equal([]) }); it('should not push proper native bid response if assets is empty', function () { let resp = { - body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'https nurl', 'burl': 'https burl', 'adm': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"link_url\"},\"assets\":[],\"imptrackers\":[\"imptrackers1\"]}}', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}}], 'seat': '44082'}]} + body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'https nurl', 'burl': 'https burl', 'adm': '{"native":{"ver":"1.1","link":{"url":"link_url"},"assets":[],"imptrackers":["imptrackers1"]}}', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}, 'adomain': ['test.com']}], 'seat': '44082'}]} }; let bids = spec.interpretResponse(resp); expect(bids).to.deep.equal([]) }); - it('should push proper native bid response', function () { + it('should push proper native bid response, assets1', function () { let resp = { - body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'https nurl', 'burl': 'https burl', 'adm': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"link_url\"},\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"title1\"}},{\"id\":2,\"required\":0,\"img\":{\"w\":80,\"h\":80,\"type\":3,\"url\":\"image_src\"}},{\"id\":3,\"required\":0,\"img\":{\"w\":50,\"h\":50,\"type\":1,\"url\":\"icon_src\"}},{\"id\":4,\"required\":0,\"data\":{\"type\":4,\"value\":\"sponsored\"}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"value\":\"price1\"}},{\"id\":6,\"required\":0,\"data\":{\"type\":7,\"value\":\"price2\"}}],\"imptrackers\":[\"imptrackers1\"]}}', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}}], 'seat': '44082'}], ext: {'muidn': 'userid'}} + body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'https nurl', 'burl': 'https burl', 'adm': '{"native":{"ver":"1.1","link":{"url":"link_url"},"assets":[{"id":1,"required":0,"title":{"text":"title1"}},{"id":2,"required":0,"img":{"w":80,"h":80,"type":3,"url":"image_src"}},{"id":3,"required":0,"img":{"w":50,"h":50,"type":1,"url":"icon_src"}},{"id":4,"required":0,"data":{"type":4,"value":"sponsored"}},{"id":5,"required":0,"data":{"type":6,"value":"price1"}},{"id":6,"required":0,"data":{"type":7,"value":"price2"}}],"imptrackers":["imptrackers1"]}}', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}, 'adomain': ['test.com']}], 'seat': '44082'}], ext: {'muidn': 'userid'}} }; let bids = spec.interpretResponse(resp); expect(bids).to.deep.equal([{ - 'ad': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"link_url\"},\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"title1\"}},{\"id\":2,\"required\":0,\"img\":{\"w\":80,\"h\":80,\"type\":3,\"url\":\"image_src\"}},{\"id\":3,\"required\":0,\"img\":{\"w\":50,\"h\":50,\"type\":1,\"url\":\"icon_src\"}},{\"id\":4,\"required\":0,\"data\":{\"type\":4,\"value\":\"sponsored\"}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"value\":\"price1\"}},{\"id\":6,\"required\":0,\"data\":{\"type\":7,\"value\":\"price2\"}}],\"imptrackers\":[\"imptrackers1\"]}}', + 'ad': '{"native":{"ver":"1.1","link":{"url":"link_url"},"assets":[{"id":1,"required":0,"title":{"text":"title1"}},{"id":2,"required":0,"img":{"w":80,"h":80,"type":3,"url":"image_src"}},{"id":3,"required":0,"img":{"w":50,"h":50,"type":1,"url":"icon_src"}},{"id":4,"required":0,"data":{"type":4,"value":"sponsored"}},{"id":5,"required":0,"data":{"type":6,"value":"price1"}},{"id":6,"required":0,"data":{"type":7,"value":"price2"}}],"imptrackers":["imptrackers1"]}}', 'burl': 'https burl', 'cpm': 1.5, 'creativeId': '2898532/2419121/2592854/2499195', @@ -598,6 +573,7 @@ describe('Mgid bid adapter', function () { 'height': 0, 'isBurl': true, 'mediaType': 'native', + 'meta': {'advertiserDomains': ['test.com']}, 'native': { 'clickTrackers': [], 'clickUrl': 'link_url', @@ -626,14 +602,14 @@ describe('Mgid bid adapter', function () { 'width': 0 }]) }); - it('should push proper native bid response', function () { + it('should push proper native bid response, assets2', function () { let resp = { - body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'https nurl', 'burl': 'https burl', 'adm': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"link_url\"},\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"title1\"}},{\"id\":2,\"required\":0,\"img\":{\"w\":80,\"h\":80,\"type\":3,\"url\":\"image_src\"}},{\"id\":3,\"required\":0,\"img\":{\"w\":50,\"h\":50,\"type\":1,\"url\":\"icon_src\"}}],\"imptrackers\":[\"imptrackers1\"]}}', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}}], 'seat': '44082'}]} + body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'https nurl', 'burl': 'https burl', 'adm': '{"native":{"ver":"1.1","link":{"url":"link_url"},"assets":[{"id":1,"required":0,"title":{"text":"title1"}},{"id":2,"required":0,"img":{"w":80,"h":80,"type":3,"url":"image_src"}},{"id":3,"required":0,"img":{"w":50,"h":50,"type":1,"url":"icon_src"}}],"imptrackers":["imptrackers1"]}}', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}, 'adomain': ['test.com']}], 'seat': '44082'}]} }; let bids = spec.interpretResponse(resp); expect(bids).to.deep.equal([ { - 'ad': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"link_url\"},\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"title1\"}},{\"id\":2,\"required\":0,\"img\":{\"w\":80,\"h\":80,\"type\":3,\"url\":\"image_src\"}},{\"id\":3,\"required\":0,\"img\":{\"w\":50,\"h\":50,\"type\":1,\"url\":\"icon_src\"}}],\"imptrackers\":[\"imptrackers1\"]}}', + 'ad': '{"native":{"ver":"1.1","link":{"url":"link_url"},"assets":[{"id":1,"required":0,"title":{"text":"title1"}},{"id":2,"required":0,"img":{"w":80,"h":80,"type":3,"url":"image_src"}},{"id":3,"required":0,"img":{"w":50,"h":50,"type":1,"url":"icon_src"}}],"imptrackers":["imptrackers1"]}}', 'cpm': 1.5, 'creativeId': '2898532/2419121/2592854/2499195', 'currency': 'GBP', @@ -641,6 +617,7 @@ describe('Mgid bid adapter', function () { 'height': 0, 'isBurl': true, 'mediaType': 'native', + 'meta': {'advertiserDomains': ['test.com']}, 'netRevenue': true, 'nurl': 'https nurl', 'burl': 'https burl', @@ -667,6 +644,36 @@ describe('Mgid bid adapter', function () { } ]); }); + + it('should not push bid response', function () { + let bids = spec.interpretResponse(); + expect(bids).to.be.undefined; + }); + it('should push proper banner bid response', function () { + let resp = { + body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': '', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'https nurl', 'burl': 'https burl', 'adm': 'html: adm', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'adomain': ['test.com']}], 'seat': '44082'}]} + }; + let bids = spec.interpretResponse(resp); + expect(bids).to.deep.equal([ + { + 'ad': 'html: adm', + 'cpm': 1.5, + 'creativeId': '2898532/2419121/2592854/2499195', + 'currency': 'USD', + 'dealId': '', + 'height': 600, + 'isBurl': true, + 'mediaType': 'banner', + 'meta': {'advertiserDomains': ['test.com']}, + 'netRevenue': true, + 'nurl': 'https nurl', + 'burl': 'https burl', + 'requestId': '61e40632c53fc2', + 'ttl': 300, + 'width': 300, + } + ]); + }); }); describe('getUserSyncs', function () { @@ -674,6 +681,7 @@ describe('Mgid bid adapter', function () { spec.getUserSyncs() }); }); + describe('on bidWon', function () { beforeEach(function() { sinon.stub(utils, 'triggerPixel'); @@ -684,7 +692,7 @@ describe('Mgid bid adapter', function () { it('should replace nurl and burl for native', function () { const burl = 'burl&s=${' + 'AUCTION_PRICE}'; const nurl = 'nurl&s=${' + 'AUCTION_PRICE}'; - const bid = {'bidderCode': 'mgid', 'width': 0, 'height': 0, 'statusMessage': 'Bid available', 'adId': '3d0b6ff1dda89', 'requestId': '2a423489e058a1', 'mediaType': 'native', 'source': 'client', 'ad': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"LinkURL\"},\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"TITLE\"}},{\"id\":2,\"required\":0,\"img\":{\"w\":80,\"h\":80,\"type\":3,\"url\":\"ImageURL\"}},{\"id\":3,\"required\":0,\"img\":{\"w\":50,\"h\":50,\"type\":1,\"url\":\"IconURL\"}},{\"id\":11,\"required\":0,\"data\":{\"type\":1,\"value\":\"sponsored\"}}],\"imptrackers\":[\"ImpTrackerURL\"]}}', 'cpm': 0.66, 'creativeId': '353538_591471', 'currency': 'USD', 'dealId': '', 'netRevenue': true, 'ttl': 300, 'nurl': nurl, 'burl': burl, 'isBurl': true, 'native': {'title': 'TITLE', 'image': {'url': 'ImageURL', 'height': 80, 'width': 80}, 'icon': {'url': 'IconURL', 'height': 50, 'width': 50}, 'sponsored': 'sponsored', 'clickUrl': 'LinkURL', 'clickTrackers': [], 'impressionTrackers': ['ImpTrackerURL'], 'jstracker': []}, 'auctionId': 'a92bffce-14d2-4f8f-a78a-7b9b5e4d28fa', 'responseTimestamp': 1556867386065, 'requestTimestamp': 1556867385916, 'bidder': 'mgid', 'adUnitCode': 'div-gpt-ad-1555415275793-0', 'timeToRespond': 149, 'pbLg': '0.50', 'pbMg': '0.60', 'pbHg': '0.66', 'pbAg': '0.65', 'pbDg': '0.66', 'pbCg': '', 'size': '0x0', 'adserverTargeting': {'hb_bidder': 'mgid', 'hb_adid': '3d0b6ff1dda89', 'hb_pb': '0.66', 'hb_size': '0x0', 'hb_source': 'client', 'hb_format': 'native', 'hb_native_title': 'TITLE', 'hb_native_image': 'hb_native_image:3d0b6ff1dda89', 'hb_native_icon': 'IconURL', 'hb_native_linkurl': 'hb_native_linkurl:3d0b6ff1dda89'}, 'status': 'targetingSet', 'params': [{'accountId': '184', 'placementId': '353538'}]}; + const bid = {'bidderCode': 'mgid', 'width': 0, 'height': 0, 'statusMessage': 'Bid available', 'adId': '3d0b6ff1dda89', 'requestId': '2a423489e058a1', 'mediaType': 'native', 'source': 'client', 'ad': '{"native":{"ver":"1.1","link":{"url":"LinkURL"},"assets":[{"id":1,"required":0,"title":{"text":"TITLE"}},{"id":2,"required":0,"img":{"w":80,"h":80,"type":3,"url":"ImageURL"}},{"id":3,"required":0,"img":{"w":50,"h":50,"type":1,"url":"IconURL"}},{"id":11,"required":0,"data":{"type":1,"value":"sponsored"}}],"imptrackers":["ImpTrackerURL"]}}', 'cpm': 0.66, 'creativeId': '353538_591471', 'currency': 'USD', 'dealId': '', 'netRevenue': true, 'ttl': 300, 'nurl': nurl, 'burl': burl, 'isBurl': true, 'native': {'title': 'TITLE', 'image': {'url': 'ImageURL', 'height': 80, 'width': 80}, 'icon': {'url': 'IconURL', 'height': 50, 'width': 50}, 'sponsored': 'sponsored', 'clickUrl': 'LinkURL', 'clickTrackers': [], 'impressionTrackers': ['ImpTrackerURL'], 'jstracker': []}, 'auctionId': 'a92bffce-14d2-4f8f-a78a-7b9b5e4d28fa', 'responseTimestamp': 1556867386065, 'requestTimestamp': 1556867385916, 'bidder': 'mgid', 'adUnitCode': 'div-gpt-ad-1555415275793-0', 'timeToRespond': 149, 'pbLg': '0.50', 'pbMg': '0.60', 'pbHg': '0.66', 'pbAg': '0.65', 'pbDg': '0.66', 'pbCg': '', 'size': '0x0', 'adserverTargeting': {'hb_bidder': 'mgid', 'hb_adid': '3d0b6ff1dda89', 'hb_pb': '0.66', 'hb_size': '0x0', 'hb_source': 'client', 'hb_format': 'native', 'hb_native_title': 'TITLE', 'hb_native_image': 'hb_native_image:3d0b6ff1dda89', 'hb_native_icon': 'IconURL', 'hb_native_linkurl': 'hb_native_linkurl:3d0b6ff1dda89'}, 'status': 'targetingSet', 'params': [{'accountId': '184', 'placementId': '353538'}]}; spec.onBidWon(bid); expect(bid.nurl).to.deep.equal('nurl&s=0.66'); expect(bid.burl).to.deep.equal('burl&s=0.66'); @@ -699,4 +707,120 @@ describe('Mgid bid adapter', function () { expect(bid.ad).to.deep.equal('burl&s=0.66'); }); }); + + describe('price floor module', function() { + let bidRequest; + let bidRequests0 = { + adUnitCode: 'div', + bidder: 'mgid', + params: { + accountId: '1', + placementId: '2', + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + sizes: [[300, 250]], + } + beforeEach(function() { + bidRequest = [utils.deepClone(bidRequests0)]; + }); + + it('obtain floor from getFloor', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'USD', + floor: 1.23 + }; + }; + + const payload = JSON.parse(spec.buildRequests(bidRequest).data); + expect(payload.imp[0]).to.have.property('bidfloor', 1.23); + expect(payload.imp[0]).to.not.have.property('bidfloorcur'); + }); + it('obtain floor from params', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'USD', + floor: 1.23 + }; + }; + bidRequest[0].params.bidfloor = 0.1; + + const payload = JSON.parse(spec.buildRequests(bidRequest).data); + expect(payload.imp[0]).to.have.property('bidfloor', 0.1); + expect(payload.imp[0]).to.not.have.property('bidfloorcur'); + }); + + it('undefined currency -> USD', function() { + bidRequest[0].params.currency = 'EUR' + bidRequest[0].getFloor = () => { + return { + floor: 1.23 + }; + }; + + const payload = JSON.parse(spec.buildRequests(bidRequest).data); + expect(payload.imp[0]).to.have.property('bidfloor', 1.23); + expect(payload.imp[0]).to.have.property('bidfloorcur', 'USD'); + }); + it('altered currency', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'EUR', + floor: 1.23 + }; + }; + + const payload = JSON.parse(spec.buildRequests(bidRequest).data); + expect(payload.imp[0]).to.have.property('bidfloor', 1.23); + expect(payload.imp[0]).to.have.property('bidfloorcur', 'EUR'); + }); + it('altered currency, same as in request', function() { + bidRequest[0].params.cur = 'EUR' + bidRequest[0].getFloor = () => { + return { + currency: 'EUR', + floor: 1.23 + }; + }; + + const payload = JSON.parse(spec.buildRequests(bidRequest).data); + expect(payload.imp[0]).to.have.property('bidfloor', 1.23); + expect(payload.imp[0]).to.not.have.property('bidfloorcur'); + }); + + it('bad floor value', function() { + bidRequest[0].getFloor = () => { + return { + currency: 'USD', + floor: 'test' + }; + }; + + const payload = JSON.parse(spec.buildRequests(bidRequest).data); + expect(payload.imp[0]).to.not.have.property('bidfloor'); + expect(payload.imp[0]).to.not.have.property('bidfloorcur'); + }); + + it('empty floor object', function() { + bidRequest[0].getFloor = () => { + return {}; + }; + + const payload = JSON.parse(spec.buildRequests(bidRequest).data); + expect(payload.imp[0]).to.not.have.property('bidfloor'); + expect(payload.imp[0]).to.not.have.property('bidfloorcur'); + }); + + it('undefined floor result', function() { + bidRequest[0].getFloor = () => {}; + + const payload = JSON.parse(spec.buildRequests(bidRequest).data); + expect(payload.imp[0]).to.not.have.property('bidfloor'); + expect(payload.imp[0]).to.not.have.property('bidfloorcur'); + }); + }); }); diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index 026e79c6d5a..86b967cca5b 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -87,6 +87,9 @@ describe('Missena Adapter', function () { cpm: 0.5, currency: 'USD', ad: '', + meta: { + advertiserDomains: ['missena.com'] + }, }; const serverTimeoutResponse = { diff --git a/test/spec/modules/mobfoxBidAdapter_spec.js b/test/spec/modules/mobfoxBidAdapter_spec.js deleted file mode 100644 index d3178d77a1a..00000000000 --- a/test/spec/modules/mobfoxBidAdapter_spec.js +++ /dev/null @@ -1,123 +0,0 @@ -describe('mobfox adapter tests', function () { - const expect = require('chai').expect; - const utils = require('src/utils'); - const adapter = require('modules/mobfoxBidAdapter'); - - const bidRequest = [{ - code: 'div-gpt-ad-1460505748561-0', - sizes: [[320, 480], [300, 250], [300, 600]], - // Replace this object to test a new Adapter! - bidder: 'mobfox', - bidId: '5t5t5t5', - params: { - s: '267d72ac3f77a3f447b32cf7ebf20673', // required - The hash of your inventory to identify which app is making the request, - imp_instl: 1 // optional - set to 1 if using interstitial otherwise delete or set to 0 - }, - placementCode: 'div-gpt-ad-1460505748561-0', - auctionId: 'c241c810-18d9-4aa4-a62f-8c1980d8d36b', - transactionId: '31f42cba-5920-4e47-adad-69c79d0d4fb4' - }]; - - describe('validRequests', function () { - let bidRequestInvalid1 = [{ - code: 'div-gpt-ad-1460505748561-0', - sizes: [[320, 480], [300, 250], [300, 600]], - // Replace this object to test a new Adapter! - bidder: 'mobfox', - bidId: '5t5t5t5', - params: { - imp_instl: 1 // optional - set to 1 if using interstitial otherwise delete or set to 0 - }, - placementCode: 'div-gpt-ad-1460505748561-0', - auctionId: 'c241c810-18d9-4aa4-a62f-8c1980d8d36b', - transactionId: '31f42cba-5920-4e47-adad-69c79d0d4fb4' - }]; - - it('test valid MF request success', function () { - let isValid = adapter.spec.isBidRequestValid(bidRequest[0]); - expect(isValid).to.equal(true); - }); - - it('test valid MF request failed1', function () { - let isValid = adapter.spec.isBidRequestValid(bidRequestInvalid1[0]); - expect(isValid).to.equal(false); - }); - }) - - describe('buildRequests', function () { - it('test build MF request', function () { - let request = adapter.spec.buildRequests(bidRequest); - let payload = request.data.split('&'); - expect(payload[0]).to.equal('rt=api-fetchip'); - expect(payload[1]).to.equal('r_type=banner'); - expect(payload[2]).to.equal('r_resp=json'); - expect(payload[3]).to.equal('s=267d72ac3f77a3f447b32cf7ebf20673'); - expect(payload[5]).to.equal('adspace_width=320'); - expect(payload[6]).to.equal('adspace_height=480'); - expect(payload[7]).to.equal('imp_instl=1'); - }); - - it('test build MF request', function () { - let request = adapter.spec.buildRequests(bidRequest); - let payload = request.data.split('&'); - expect(payload[0]).to.equal('rt=api-fetchip'); - expect(payload[1]).to.equal('r_type=banner'); - expect(payload[2]).to.equal('r_resp=json'); - expect(payload[3]).to.equal('s=267d72ac3f77a3f447b32cf7ebf20673'); - expect(payload[5]).to.equal('adspace_width=320'); - expect(payload[6]).to.equal('adspace_height=480'); - expect(payload[7]).to.equal('imp_instl=1'); - }); - }) - - describe('interceptResponse', function () { - let mockServerResponse = { - body: { - request: { - clicktype: 'safari', - clickurl: 'https://tokyo-my.mobfox.com/exchange.click.php?h=494ef76d5b0287a8b5ac8724855cb5e0', - cpmPrice: 50, - htmlString: 'test', - refresh: '30', - scale: 'no', - skippreflight: 'yes', - type: 'textAd', - urltype: 'link' - } - }, - headers: { - get: function (header) { - if (header === 'X-Pricing-CPM') { - return 50; - } - } - } - }; - it('test intercept response', function () { - let request = adapter.spec.buildRequests(bidRequest); - let bidResponses = adapter.spec.interpretResponse(mockServerResponse, request); - expect(bidResponses.length).to.equal(1); - expect(bidResponses[0].ad).to.equal('test'); - expect(bidResponses[0].cpm).to.equal(50); - expect(bidResponses[0].creativeId).to.equal('267d72ac3f77a3f447b32cf7ebf20673'); - expect(bidResponses[0].requestId).to.equal('5t5t5t5'); - expect(bidResponses[0].currency).to.equal('USD'); - expect(bidResponses[0].height).to.equal('480'); - expect(bidResponses[0].netRevenue).to.equal(true); - expect(bidResponses[0].referrer).to.equal('https://tokyo-my.mobfox.com/exchange.click.php?h=494ef76d5b0287a8b5ac8724855cb5e0'); - expect(bidResponses[0].ttl).to.equal(360); - expect(bidResponses[0].width).to.equal('320'); - }); - - it('test intercept response with empty server response', function () { - let request = adapter.spec.buildRequests(bidRequest); - let serverResponse = { - request: { - error: 'cannot get response' - } - }; - let bidResponses = adapter.spec.interpretResponse(serverResponse, request); - expect(bidResponses.length).to.equal(0); - }) - }) -}); diff --git a/test/spec/modules/mobsmartBidAdapter_spec.js b/test/spec/modules/mobsmartBidAdapter_spec.js deleted file mode 100644 index b48878adff6..00000000000 --- a/test/spec/modules/mobsmartBidAdapter_spec.js +++ /dev/null @@ -1,214 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/mobsmartBidAdapter.js'; - -describe('mobsmartBidAdapter', function () { - describe('isBidRequestValid', function () { - let bid; - beforeEach(function() { - bid = { - bidder: 'mobsmart', - params: { - floorPrice: 100, - currency: 'JPY' - }, - mediaTypes: { - banner: { - size: [[300, 250]] - } - } - }; - }); - - it('should return true when valid bid request is set', function() { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when bidder is not set to "mobsmart"', function() { - bid.bidder = 'bidder'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return true when params are not set', function() { - delete bid.params.floorPrice; - delete bid.params.currency; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - }); - - describe('buildRequests', function () { - let bidRequests; - beforeEach(function() { - bidRequests = [ - { - bidder: 'mobsmart', - adUnitCode: 'mobsmart-ad-code', - auctionId: 'auctionid-123', - bidId: 'bidid123', - bidRequestsCount: 1, - bidderRequestId: 'bidderrequestid123', - transactionId: 'transaction-id-123', - sizes: [[300, 250]], - requestId: 'requestid123', - params: { - floorPrice: 100, - currency: 'JPY' - }, - mediaTypes: { - banner: { - size: [[300, 250]] - } - }, - userId: { - pubcid: 'pubc-id-123' - } - }, { - bidder: 'mobsmart', - adUnitCode: 'mobsmart-ad-code2', - auctionId: 'auctionid-456', - bidId: 'bidid456', - bidRequestsCount: 1, - bidderRequestId: 'bidderrequestid456', - transactionId: 'transaction-id-456', - sizes: [[320, 50]], - requestId: 'requestid456', - params: { - floorPrice: 100, - currency: 'JPY' - }, - mediaTypes: { - banner: { - size: [[320, 50]] - } - }, - userId: { - pubcid: 'pubc-id-456' - } - } - ]; - }); - - let bidderRequest = { - refererInfo: { - referer: 'https://example.com' - } - }; - - it('should not contain a sizes when sizes is not set', function() { - delete bidRequests[0].sizes; - delete bidRequests[1].sizes; - let requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests[0].data.sizes).to.be.an('undefined'); - expect(requests[1].data.sizes).to.be.an('undefined'); - }); - - it('should not contain a userId when userId is not set', function() { - delete bidRequests[0].userId; - delete bidRequests[1].userId; - let requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests[0].data.userId).to.be.an('undefined'); - expect(requests[1].data.userId).to.be.an('undefined'); - }); - - it('should have a post method', function() { - let requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests[0].method).to.equal('POST'); - expect(requests[1].method).to.equal('POST'); - }); - - it('should contain a request id equals to the bid id', function() { - let requests = spec.buildRequests(bidRequests, bidderRequest); - expect(JSON.parse(requests[0].data).requestId).to.equal(bidRequests[0].bidId); - expect(JSON.parse(requests[1].data).requestId).to.equal(bidRequests[1].bidId); - }); - - it('should have an url that match the default endpoint', function() { - let requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests[0].url).to.equal('https://prebid.mobsmart.net/prebid/endpoint'); - expect(requests[1].url).to.equal('https://prebid.mobsmart.net/prebid/endpoint'); - }); - }); - - describe('interpretResponse', function () { - let serverResponse; - beforeEach(function() { - serverResponse = { - body: { - 'requestId': 'request-id', - 'cpm': 100, - 'width': 300, - 'height': 250, - 'ad': '
ad
', - 'ttl': 300, - 'creativeId': 'creative-id', - 'netRevenue': true, - 'currency': 'JPY' - } - }; - }); - - it('should return a valid response', () => { - var responses = spec.interpretResponse(serverResponse); - expect(responses).to.be.an('array').that.is.not.empty; - - let response = responses[0]; - expect(response).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency'); - expect(response.requestId).to.equal('request-id'); - expect(response.cpm).to.equal(100); - expect(response.width).to.equal(300); - expect(response.height).to.equal(250); - expect(response.ad).to.equal('
ad
'); - expect(response.ttl).to.equal(300); - expect(response.creativeId).to.equal('creative-id'); - expect(response.netRevenue).to.be.true; - expect(response.currency).to.equal('JPY'); - }); - - it('should return an empty array when serverResponse is empty', () => { - serverResponse = {}; - var responses = spec.interpretResponse(serverResponse); - expect(responses).to.deep.equal([]); - }); - }); - - describe('getUserSyncs', function () { - it('should return nothing when sync is disabled', function () { - const syncOptions = { - 'iframeEnabled': false, - 'pixelEnabled': false - } - let syncs = spec.getUserSyncs(syncOptions); - expect(syncs).to.deep.equal([]); - }); - - it('should register iframe sync when iframe is enabled', function () { - const syncOptions = { - 'iframeEnabled': true, - 'pixelEnabled': false - } - let syncs = spec.getUserSyncs(syncOptions); - expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.equal('https://tags.mobsmart.net/tags/iframe'); - }); - - it('should register image sync when image is enabled', function () { - const syncOptions = { - 'iframeEnabled': false, - 'pixelEnabled': true - } - let syncs = spec.getUserSyncs(syncOptions); - expect(syncs[0].type).to.equal('image'); - expect(syncs[0].url).to.equal('https://tags.mobsmart.net/tags/image'); - }); - - it('should register iframe sync when iframe is enabled', function () { - const syncOptions = { - 'iframeEnabled': true, - 'pixelEnabled': true - } - let syncs = spec.getUserSyncs(syncOptions); - expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.equal('https://tags.mobsmart.net/tags/iframe'); - }); - }); -}); diff --git a/test/spec/modules/multibid_spec.js b/test/spec/modules/multibid_spec.js index e849392ee4b..86365eb520f 100644 --- a/test/spec/modules/multibid_spec.js +++ b/test/spec/modules/multibid_spec.js @@ -82,7 +82,7 @@ describe('multibid adapter', function () { 'sizes': [[300, 250]] } }, - 'adUnitCode': 'test-div', + 'adUnitCode': 'test.div', 'transactionId': 'c153f3da-84f0-4be8-95cb-0647c458bc60', 'sizes': [[300, 250]], 'bidId': '2408ef83b84c9d', @@ -106,7 +106,7 @@ describe('multibid adapter', function () { 'sizes': [[300, 250]] } }, - 'adUnitCode': 'test-div', + 'adUnitCode': 'test.div', 'transactionId': 'c153f3da-84f0-4be8-95cb-0647c458bc60', 'sizes': [[300, 250]], 'bidId': '2408ef83b84c9d', @@ -197,14 +197,14 @@ describe('multibid adapter', function () { }); it('adds original bids and does not modify', function () { - let adUnitCode = 'test-div'; + let adUnitCode = 'test.div'; let bids = [{...bidArray[0]}, {...bidArray[1]}]; addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid).to.deep.equal(bids[0]); @@ -214,13 +214,13 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid).to.deep.equal(bids[1]); }); it('modifies and adds both bids based on multibid configuration', function () { - let adUnitCode = 'test-div'; + let adUnitCode = 'test.div'; let bids = [{...bidArray[0]}, {...bidArray[1]}]; config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); @@ -232,7 +232,7 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid).to.deep.equal(bids[0]); @@ -250,13 +250,13 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid).to.deep.equal(bids[1]); }); it('only modifies bids defined in the multibid configuration', function () { - let adUnitCode = 'test-div'; + let adUnitCode = 'test.div'; let bids = [{...bidArray[0]}, {...bidArray[1]}]; bids.push({ @@ -276,7 +276,7 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid).to.deep.equal(bids[0]); @@ -292,7 +292,7 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid).to.deep.equal(bids[1]); @@ -302,13 +302,13 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid).to.deep.equal(bids[2]); }); it('only modifies and returns bids under limit for a specifc bidder in the multibid configuration', function () { - let adUnitCode = 'test-div'; + let adUnitCode = 'test.div'; let bids = [{...bidArray[0]}, {...bidArray[1]}]; bids.push({ @@ -328,7 +328,7 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid).to.deep.equal(bids[0]); @@ -344,7 +344,7 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid).to.deep.equal(bids[1]); @@ -356,7 +356,7 @@ describe('multibid adapter', function () { }); it('if no prefix in multibid configuration, modifies and returns bids under limit without preifx property', function () { - let adUnitCode = 'test-div'; + let adUnitCode = 'test.div'; let bids = [{...bidArray[0]}, {...bidArray[1]}]; bids.push({ @@ -375,7 +375,7 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid).to.deep.equal(bids[0]); @@ -389,7 +389,7 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid).to.deep.equal(bids[1]); @@ -401,7 +401,7 @@ describe('multibid adapter', function () { }); it('does not include extra bids if cpm is less than floor value', function () { - let adUnitCode = 'test-div'; + let adUnitCode = 'test.div'; let bids = [{...bidArrayAlt[1]}, {...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}]; bids.map(bid => { @@ -418,7 +418,7 @@ describe('multibid adapter', function () { floorRuleValue: 65, floorValue: 65, matchedFields: { - gptSlot: 'test-div', + gptSlot: 'test.div', mediaType: 'banner' } } @@ -435,7 +435,7 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid.bidder).to.equal('bidderA'); expect(result.bid.targetingBidder).to.equal(undefined); @@ -452,7 +452,7 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid.bidder).to.equal('bidderB'); expect(result.bid.targetingBidder).to.equal(undefined); @@ -463,14 +463,14 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid.bidder).to.equal('bidderC'); expect(result.bid.targetingBidder).to.equal(undefined); }); it('does include extra bids if cpm is not less than floor value', function () { - let adUnitCode = 'test-div'; + let adUnitCode = 'test.div'; let bids = [{...bidArrayAlt[1]}, {...bidArrayAlt[0]}]; bids.map(bid => { @@ -487,7 +487,7 @@ describe('multibid adapter', function () { floorRuleValue: 25, floorValue: 25, matchedFields: { - gptSlot: 'test-div', + gptSlot: 'test.div', mediaType: 'banner' } } @@ -504,7 +504,7 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid.bidder).to.equal('bidderA'); expect(result.bid.targetingBidder).to.equal(undefined); @@ -518,7 +518,7 @@ describe('multibid adapter', function () { expect(result).to.not.equal(null); expect(result.adUnitCode).to.not.equal(null); - expect(result.adUnitCode).to.equal('test-div'); + expect(result.adUnitCode).to.equal('test.div'); expect(result.bid).to.not.equal(null); expect(result.bid.bidder).to.equal('bidderA'); expect(result.bid.targetingBidder).to.equal('bidA2'); @@ -578,7 +578,7 @@ describe('multibid adapter', function () { it('it sorts and creates dynamic alias on bidsReceived if multibid configuration found with prefix', function () { let modifiedBids = [{...bidArray[1]}, {...bidArray[0]}].map(bid => { - addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + addBidResponseHook(bidResponseCallback, 'test.div', {...bid}); return bidResult; }); @@ -603,7 +603,7 @@ describe('multibid adapter', function () { it('it sorts by cpm treating dynamic alias as unique bid when no bid limit defined', function () { let modifiedBids = [{...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}, {...bidArrayAlt[1]}].map(bid => { - addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + addBidResponseHook(bidResponseCallback, 'test.div', {...bid}); return bidResult; }); @@ -636,7 +636,7 @@ describe('multibid adapter', function () { it('it should filter out dynamic bid when bid limit is less than unique bid pool', function () { let modifiedBids = [{...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}, {...bidArrayAlt[1]}].map(bid => { - addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + addBidResponseHook(bidResponseCallback, 'test.div', {...bid}); return bidResult; }); @@ -662,7 +662,7 @@ describe('multibid adapter', function () { config.setConfig({ multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}] }); let modifiedBids = [{...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}, {...bidArrayAlt[1]}].map(bid => { - addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + addBidResponseHook(bidResponseCallback, 'test.div', {...bid}); return bidResult; }); diff --git a/test/spec/modules/mytargetBidAdapter_spec.js b/test/spec/modules/mytargetBidAdapter_spec.js index ea998303fe3..62d139bb926 100644 --- a/test/spec/modules/mytargetBidAdapter_spec.js +++ b/test/spec/modules/mytargetBidAdapter_spec.js @@ -1,6 +1,5 @@ import { expect } from 'chai'; -import { config } from 'src/config.js'; -import { spec } from 'modules/mytargetBidAdapter.js'; +import { spec } from 'modules/mytargetBidAdapter'; describe('MyTarget Adapter', function() { describe('isBidRequestValid', function () { @@ -55,7 +54,7 @@ describe('MyTarget Adapter', function() { it('should build single POST request for multiple bids', function() { expect(bidRequest.method).to.equal('POST'); - expect(bidRequest.url).to.equal('https://ad.mail.ru/hbid_prebid/'); + expect(bidRequest.url).to.equal('//ad.mail.ru/hbid_prebid/'); expect(bidRequest.data).to.be.an('object'); expect(bidRequest.data.places).to.be.an('array'); expect(bidRequest.data.places).to.have.lengthOf(2); @@ -115,74 +114,45 @@ describe('MyTarget Adapter', function() { expect(settings.windowSize.width).to.equal(window.screen.width); expect(settings.windowSize.height).to.equal(window.screen.height); }); - - it('should pass currency from currency.adServerCurrency', function() { - const configStub = sinon.stub(config, 'getConfig').callsFake( - key => key === 'currency.adServerCurrency' ? 'USD' : ''); - - let bidRequest = spec.buildRequests(bidRequests, bidderRequest); - let settings = bidRequest.data.settings; - - expect(settings).to.be.an('object'); - expect(settings.currency).to.equal('USD'); - expect(settings.windowSize).to.be.an('object'); - expect(settings.windowSize.width).to.equal(window.screen.width); - expect(settings.windowSize.height).to.equal(window.screen.height); - - configStub.restore(); - }); - - it('should ignore currency other than "RUB" or "USD"', function() { - const configStub = sinon.stub(config, 'getConfig').callsFake( - key => key === 'currency.adServerCurrency' ? 'EUR' : ''); - - let bidRequest = spec.buildRequests(bidRequests, bidderRequest); - let settings = bidRequest.data.settings; - - expect(settings).to.be.an('object'); - expect(settings.currency).to.equal('RUB'); - - configStub.restore(); - }); }); describe('interpretResponse', function () { let serverResponse = { body: { 'bidder_status': - [ - { - 'bidder': 'mail.ru', - 'response_time_ms': 100, - 'num_bids': 2 - } - ], + [ + { + 'bidder': 'mail.ru', + 'response_time_ms': 100, + 'num_bids': 2 + } + ], 'bids': - [ - { - 'displayUrl': 'https://ad.mail.ru/hbid_imp/12345', - 'size': + [ { - 'height': '400', - 'width': '240' + 'displayUrl': 'https://ad.mail.ru/hbid_imp/12345', + 'size': + { + 'height': '400', + 'width': '240' + }, + 'id': '1', + 'currency': 'RUB', + 'price': 100, + 'ttl': 360, + 'creativeId': '123456' }, - 'id': '1', - 'currency': 'RUB', - 'price': 100, - 'ttl': 360, - 'creativeId': '123456' - }, - { - 'adm': '

Ad

', - 'size': { - 'height': '250', - 'width': '300' - }, - 'id': '2', - 'price': 200 - } - ] + 'adm': '

Ad

', + 'size': + { + 'height': '250', + 'width': '300' + }, + 'id': '2', + 'price': 200 + } + ] } }; diff --git a/test/spec/modules/nafdigitalBidAdapter_spec.js b/test/spec/modules/nafdigitalBidAdapter_spec.js deleted file mode 100644 index c8ffb9fbbaf..00000000000 --- a/test/spec/modules/nafdigitalBidAdapter_spec.js +++ /dev/null @@ -1,283 +0,0 @@ -import { expect } from 'chai'; -import * as utils from 'src/utils.js'; -import { spec } from 'modules/nafdigitalBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -const URL = 'https://nafdigitalbidder.com/hb'; - -describe('nafdigitalBidAdapter', function() { - const adapter = newBidder(spec); - let element, win; - let bidRequests; - let sandbox; - - beforeEach(function() { - element = { - x: 0, - y: 0, - - width: 0, - height: 0, - - getBoundingClientRect: () => { - return { - width: element.width, - height: element.height, - - left: element.x, - top: element.y, - right: element.x + element.width, - bottom: element.y + element.height - }; - } - }; - win = { - document: { - visibilityState: 'visible' - }, - - innerWidth: 800, - innerHeight: 600 - }; - bidRequests = [{ - 'bidder': 'nafdigital', - 'params': { - 'publisherId': 1234567 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '5fb26ac22bde4', - 'bidderRequestId': '4bf93aeb730cb9', - 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e' - }]; - - sandbox = sinon.sandbox.create(); - sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); - sandbox.stub(utils, 'getWindowTop').returns(win); - sandbox.stub(utils, 'getWindowSelf').returns(win); - }); - - afterEach(function() { - sandbox.restore(); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'nafdigital', - 'params': { - 'publisherId': 1234567 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '5fb26ac22bde4', - 'bidderRequestId': '4bf93aeb730cb9', - 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when tagid not passed correctly', function () { - bid.params.publisherId = undefined; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - it('sends bid request to our endpoint via POST', function () { - const request = spec.buildRequests(bidRequests); - expect(request.method).to.equal('POST'); - }); - - it('request url should match our endpoint url', function () { - const request = spec.buildRequests(bidRequests); - expect(request.url).to.equal(URL); - }); - - it('sets the proper banner object', function() { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); - }); - - it('accepts a single array as a size', function() { - bidRequests[0].sizes = [300, 250]; - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); - }); - - it('sends bidfloor param if present', function () { - bidRequests[0].params.bidFloor = 0.05; - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].bidfloor).to.equal(0.05); - }); - - it('sends tagid', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].tagid).to.equal('adunit-code'); - }); - - it('sends publisher id', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.site.publisher.id).to.equal(1234567); - }); - - context('when element is fully in view', function() { - it('returns 100', function() { - Object.assign(element, { width: 600, height: 400 }); - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(100); - }); - }); - - context('when element is out of view', function() { - it('returns 0', function() { - Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(0); - }); - }); - - context('when element is partially in view', function() { - it('returns percentage', function() { - Object.assign(element, { width: 800, height: 800 }); - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(75); - }); - }); - - context('when width or height of the element is zero', function() { - it('try to use alternative values', function() { - Object.assign(element, { width: 0, height: 0 }); - bidRequests[0].sizes = [[800, 2400]]; - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(25); - }); - }); - - context('when nested iframes', function() { - it('returns \'na\'', function() { - Object.assign(element, { width: 600, height: 400 }); - - utils.getWindowTop.restore(); - utils.getWindowSelf.restore(); - sandbox.stub(utils, 'getWindowTop').returns(win); - sandbox.stub(utils, 'getWindowSelf').returns({}); - - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal('na'); - }); - }); - - context('when tab is inactive', function() { - it('returns 0', function() { - Object.assign(element, { width: 600, height: 400 }); - - utils.getWindowTop.restore(); - win.document.visibilityState = 'hidden'; - sandbox.stub(utils, 'getWindowTop').returns(win); - - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(0); - }); - }); - }); - - describe('interpretResponse', function () { - let response; - beforeEach(function () { - response = { - body: { - 'id': '37386aade21a71', - 'seatbid': [{ - 'bid': [{ - 'id': '376874781', - 'impid': '283a9f4cd2415d', - 'price': 0.35743275, - 'nurl': '', - 'adm': '', - 'w': 300, - 'h': 250 - }] - }] - } - }; - }); - - it('should get the correct bid response', function () { - let expectedResponse = [{ - 'requestId': '283a9f4cd2415d', - 'cpm': 0.35743275, - 'width': 300, - 'height': 250, - 'creativeId': '376874781', - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ad': `
`, - 'ttl': 60 - }]; - - let result = spec.interpretResponse(response); - expect(result[0]).to.deep.equal(expectedResponse[0]); - }); - - it('crid should default to the bid id if not on the response', function () { - let expectedResponse = [{ - 'requestId': '283a9f4cd2415d', - 'cpm': 0.35743275, - 'width': 300, - 'height': 250, - 'creativeId': response.body.seatbid[0].bid[0].id, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ad': `
`, - 'ttl': 60 - }]; - - let result = spec.interpretResponse(response); - expect(result[0]).to.deep.equal(expectedResponse[0]); - }); - - it('handles empty bid response', function () { - let response = { - body: '' - }; - let result = spec.interpretResponse(response); - expect(result.length).to.equal(0); - }); - }); - - describe('getUserSyncs ', () => { - let syncOptions = {iframeEnabled: true, pixelEnabled: true}; - - it('should not return', () => { - let returnStatement = spec.getUserSyncs(syncOptions, []); - expect(returnStatement).to.be.empty; - }); - }); -}); diff --git a/test/spec/modules/nanointeractiveBidAdapter_spec.js b/test/spec/modules/nanointeractiveBidAdapter_spec.js deleted file mode 100644 index 715a26a4597..00000000000 --- a/test/spec/modules/nanointeractiveBidAdapter_spec.js +++ /dev/null @@ -1,466 +0,0 @@ -import {expect} from 'chai'; -import * as utils from 'src/utils.js'; -import * as sinon from 'sinon'; - -import { - BIDDER_CODE, - CATEGORY, - CATEGORY_NAME, - SSP_PLACEMENT_ID, - END_POINT_URL, - NQ, - NQ_NAME, - REF, - spec, - SUB_ID -} from '../../../modules/nanointeractiveBidAdapter.js'; - -describe('nanointeractive adapter tests', function () { - const SIZES_PARAM = 'sizes'; - const BID_ID_PARAM = 'bidId'; - const BID_ID_VALUE = '24a1c9ec270973'; - const DATA_PARTNER_PIXEL_ID_VALUE = 'testPID'; - const NQ_VALUE = 'rumpelstiltskin'; - const NQ_NAME_QUERY_PARAM = 'nqName'; - const CATEGORY_VALUE = 'some category'; - const CATEGORY_NAME_QUERY_PARAM = 'catName'; - const SUB_ID_VALUE = '123'; - const REF_NO_VALUE = 'none'; - const REF_OTHER_VALUE = 'other'; - const WIDTH1 = 300; - const HEIGHT1 = 250; - const WIDTH2 = 468; - const HEIGHT2 = 60; - const SIZES_VALUE = [[WIDTH1, HEIGHT1], [WIDTH2, HEIGHT2]]; - const AD = ' '; - const CPM = 1; - - function getBidRequest(params) { - return { - bidder: BIDDER_CODE, - params: params, - placementCode: 'div-gpt-ad-1460505748561-0', - transactionId: 'ee335735-ddd3-41f2-b6c6-e8aa99f81c0f', - [SIZES_PARAM]: SIZES_VALUE, - [BID_ID_PARAM]: BID_ID_VALUE, - bidderRequestId: '189135372acd55', - auctionId: 'ac15bb68-4ef0-477f-93f4-de91c47f00a9' - }; - } - - describe('NanoAdapter', function () { - let nanoBidAdapter = spec; - - describe('Methods', function () { - it('Test isBidRequestValid() with valid param(s): pid', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nq', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nq, category', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ, - [CATEGORY]: CATEGORY_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nq, categoryName', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ, - [CATEGORY_NAME_QUERY_PARAM]: CATEGORY_NAME_QUERY_PARAM, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nq, subId', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ, - [SUB_ID]: SUB_ID_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nqName', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ_NAME]: NQ_NAME_QUERY_PARAM, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nqName, category', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ_NAME]: NQ_NAME_QUERY_PARAM, - [CATEGORY]: CATEGORY_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nqName, categoryName', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ_NAME]: NQ_NAME_QUERY_PARAM, - [CATEGORY_NAME_QUERY_PARAM]: CATEGORY_NAME_QUERY_PARAM, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nqName, subId', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ_NAME]: NQ_NAME_QUERY_PARAM, - [SUB_ID]: SUB_ID_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, category', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [CATEGORY]: CATEGORY_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, category, subId', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [CATEGORY]: CATEGORY_VALUE, - [SUB_ID]: SUB_ID_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, subId', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [SUB_ID]: SUB_ID_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nq, category, subId', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ_VALUE, - [CATEGORY]: CATEGORY_VALUE, - [SUB_ID]: SUB_ID_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nqName, categoryName, subId', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ_NAME]: NQ_NAME_QUERY_PARAM, - [CATEGORY_NAME]: CATEGORY_NAME_QUERY_PARAM, - [SUB_ID]: SUB_ID_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nq, category, subId, ref (value none)', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ_VALUE, - [CATEGORY]: CATEGORY_VALUE, - [SUB_ID]: SUB_ID_VALUE, - [REF]: REF_NO_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with valid param(s): pid, nq, category, subId, ref (value other)', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ_VALUE, - [CATEGORY]: CATEGORY_VALUE, - [SUB_ID]: SUB_ID_VALUE, - [REF]: REF_OTHER_VALUE, - }))).to.equal(true); - }); - it('Test isBidRequestValid() with invalid param(s): empty', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({}))).to.equal(false); - }); - it('Test isBidRequestValid() with invalid param(s): pid missing', function () { - expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [NQ]: NQ_VALUE, - [CATEGORY]: CATEGORY_VALUE, - [SUB_ID]: SUB_ID_VALUE, - }))).to.equal(false); - }); - - let sandbox; - - function getMocks() { - let mockOriginAddress = 'https://localhost'; - let mockRefAddress = 'https://some-ref.test'; - return { - 'windowLocationAddress': mockRefAddress, - 'originAddress': mockOriginAddress, - 'refAddress': '', - }; - } - - function setUpMocks() { - sinon.sandbox.restore(); - sandbox = sinon.sandbox.create(); - sandbox.stub(utils, 'getOrigin').callsFake(() => getMocks()['originAddress']); - sandbox.stub(utils, 'deepAccess').callsFake(() => getMocks()['windowLocationAddress']); - - sandbox.stub(utils, 'getParameterByName').callsFake((arg) => { - switch (arg) { - case CATEGORY_NAME_QUERY_PARAM: - return CATEGORY_VALUE; - case NQ_NAME_QUERY_PARAM: - return NQ_VALUE; - } - }); - } - - function assert( - request, - expectedPid, - expectedNq, - expectedCategory, - expectedSubId - ) { - const requestData = JSON.parse(request.data); - - expect(request.method).to.equal('POST'); - expect(request.url).to.equal(END_POINT_URL + '/hb'); - expect(requestData[0].pid).to.equal(expectedPid); - expect(requestData[0].nq.toString()).to.equal(expectedNq.toString()); - expect(requestData[0].category.toString()).to.equal(expectedCategory.toString()); - expect(requestData[0].subId).to.equal(expectedSubId); - } - - function tearDownMocks() { - sandbox.restore(); - } - - it('Test buildRequest() - pid', function () { - setUpMocks(); - let requestParams = { - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [null]; - let expectedCategory = [null]; - let expectedSubId = null; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId); - tearDownMocks(); - }); - it('Test buildRequest() - pid, nq', function () { - setUpMocks(); - let requestParams = { - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ_VALUE, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [NQ_VALUE]; - let expectedCategory = [null]; - let expectedSubId = null; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId); - tearDownMocks(); - }); - it('Test buildRequest() - pid, nq, category', function () { - setUpMocks(); - let requestParams = { - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ_VALUE, - [CATEGORY]: CATEGORY_VALUE, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [NQ_VALUE]; - let expectedCategory = [CATEGORY_VALUE]; - let expectedSubId = null; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId); - tearDownMocks(); - }); - it('Test buildRequest() - pid, nq, categoryName', function () { - setUpMocks(); - - let requestParams = { - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ_VALUE, - [CATEGORY_NAME]: CATEGORY_NAME_QUERY_PARAM, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [NQ_VALUE]; - let expectedCategory = [CATEGORY_VALUE]; - let expectedSubId = null; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId); - tearDownMocks(); - }); - it('Test buildRequest() - pid, nq, subId', function () { - setUpMocks(); - let requestParams = { - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ_VALUE, - [SUB_ID]: SUB_ID_VALUE, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [NQ_VALUE]; - let expectedCategory = [null]; - let expectedSubId = SUB_ID_VALUE; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId); - tearDownMocks(); - }); - it('Test buildRequest() - pid, category', function () { - setUpMocks(); - let requestParams = { - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [CATEGORY]: CATEGORY_VALUE, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [null]; - let expectedCategory = [CATEGORY_VALUE]; - let expectedSubId = null; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId); - tearDownMocks(); - }); - it('Test buildRequest() - pid, category, subId', function () { - setUpMocks(); - let requestParams = { - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [CATEGORY]: CATEGORY_VALUE, - [SUB_ID]: SUB_ID_VALUE, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [null]; - let expectedCategory = [CATEGORY_VALUE]; - let expectedSubId = SUB_ID_VALUE; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId); - tearDownMocks(); - }); - it('Test buildRequest() - pid, subId', function () { - setUpMocks(); - let requestParams = { - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [SUB_ID]: SUB_ID_VALUE, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [null]; - let expectedCategory = [null]; - let expectedSubId = SUB_ID_VALUE; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId); - tearDownMocks(); - }); - it('Test buildRequest() - pid, nq, category, subId', function () { - setUpMocks(); - let requestParams = { - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ_VALUE, - [CATEGORY]: CATEGORY_VALUE, - [SUB_ID]: SUB_ID_VALUE, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [NQ_VALUE]; - let expectedCategory = [CATEGORY_VALUE]; - let expectedSubId = SUB_ID_VALUE; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId); - tearDownMocks(); - }); - it('Test buildRequest() - pid, nqName, categoryName, subId', function () { - setUpMocks(); - let requestParams = { - [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ_NAME]: NQ_NAME_QUERY_PARAM, - [CATEGORY_NAME]: CATEGORY_NAME_QUERY_PARAM, - [SUB_ID]: SUB_ID_VALUE, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [NQ_VALUE]; - let expectedCategory = [CATEGORY_VALUE]; - let expectedSubId = SUB_ID_VALUE; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId); - tearDownMocks(); - }); - it('Test interpretResponse() length', function () { - let bids = nanoBidAdapter.interpretResponse({ - body: [ - // valid - { - id: '24a1c9ec270973', - cpm: CPM, - width: WIDTH1, - height: HEIGHT1, - ad: AD, - ttl: 360, - creativeId: 'TEST_ID', - netRevenue: false, - currency: 'EUR', - }, - // invalid - { - id: '24a1c9ec270973', - cpm: null, - width: WIDTH1, - height: HEIGHT1, - ad: AD, - ttl: 360, - creativeId: 'TEST_ID', - netRevenue: false, - currency: 'EUR', - } - ] - }); - expect(bids.length).to.equal(1); - }); - it('Test interpretResponse() bids', function () { - let bid = nanoBidAdapter.interpretResponse({ - body: [ - // valid - { - id: '24a1c9ec270973', - cpm: CPM, - width: WIDTH1, - height: HEIGHT1, - ad: AD, - ttl: 360, - creativeId: 'TEST_ID', - netRevenue: false, - currency: 'EUR', - }, - // invalid - { - id: '24a1c9ec270973', - cpm: null, - width: WIDTH1, - height: HEIGHT1, - ad: AD, - ttl: 360, - creativeId: 'TEST_ID', - netRevenue: false, - currency: 'EUR', - } - ] - })[0]; - expect(bid.requestId).to.equal('24a1c9ec270973'); - expect(bid.cpm).to.equal(CPM); - expect(bid.width).to.equal(WIDTH1); - expect(bid.height).to.equal(HEIGHT1); - expect(bid.ad).to.equal(AD); - expect(bid.ttl).to.equal(360); - expect(bid.creativeId).to.equal('TEST_ID'); - expect(bid.currency).to.equal('EUR'); - }); - }); - }); -}); diff --git a/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js b/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js deleted file mode 100644 index 4731b1a77d3..00000000000 --- a/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js +++ /dev/null @@ -1,143 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/nasmediaAdmixerBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; - -describe('nasmediaAdmixerBidAdapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - const bid = { - 'bidder': 'nasmediaAdmixer', - 'params': { - 'media_key': 'media_key', - 'adunit_id': 'adunit_id', - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250]], - 'bidId': '3361d01e67dbd6', - 'bidderRequestId': '2b60dcd392628a', - 'auctionId': '124cb070528662', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - const bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'media_key': '', - 'adunit_id': '', - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - const bidRequests = [ - { - 'bidder': 'nasmediaAdmixer', - 'params': { - 'media_key': '19038695', - 'adunit_id': '24190632', - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250]], - 'bidId': '3361d01e67dbd6', - 'bidderRequestId': '2b60dcd392628a', - 'auctionId': '124cb070528662', - } - ]; - const bidderRequest = {refererInfo: {referer: 'https://example.com'}}; - - it('sends bid request to url via GET', function () { - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.method).to.equal('GET'); - expect(request.url).to.match(new RegExp(`https://adn.admixer.co.kr`)); - }); - }); - - describe('interpretResponse', function () { - const response = { - 'body': { - 'bidder': 'nasmediaAdmixer', - 'req_id': '861a8e7952c82c', - 'error_code': 0, - 'error_msg': 'OK', - 'body': [{ - 'ad_id': '20049', - 'width': 300, - 'height': 250, - 'currency': 'USD', - 'cpm': 1.769221, - 'ad': '' - }] - }, - 'headers': { - 'get': function () { - } - } - }; - - const bidRequest = { - 'bidder': 'nasmediaAdmixer', - 'params': { - 'media_key': '19038695', - 'adunit_id': '24190632', - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [320, 480]], - 'bidId': '31300c8b9697cd', - 'bidderRequestId': '2bf570adcf83fa', - 'auctionId': '169827a33f03cc', - }; - - it('should get correct bid response', function () { - const expectedResponse = [ - { - 'requestId': '861a8e7952c82c', - 'cpm': 1.769221, - 'currency': 'USD', - 'width': 300, - 'height': 250, - 'ad': '', - 'creativeId': '20049', - 'ttl': 360, - 'netRevenue': false - } - ]; - - const result = spec.interpretResponse(response, bidRequest); - expect(result).to.have.lengthOf(1); - let resultKeys = Object.keys(result[0]); - expect(resultKeys.sort()).to.deep.equal(Object.keys(expectedResponse[0]).sort()); - resultKeys.forEach(function (k) { - if (k === 'ad') { - expect(result[0][k]).to.match(/$/); - } else { - expect(result[0][k]).to.equal(expectedResponse[0][k]); - } - }); - }); - - it('handles nobid responses', function () { - response.body = { - 'bidder': 'nasmediaAdmixer', - 'req_id': '861a8e7952c82c', - 'error_code': 0, - 'error_msg': 'OK', - 'body': [] - }; - - const result = spec.interpretResponse(response, bidRequest); - expect(result).to.have.lengthOf(0); - }); - }); -}); diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js index e1132bf1b74..23f48f3661a 100644 --- a/test/spec/modules/nativoBidAdapter_spec.js +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -8,29 +8,46 @@ import { spec } from 'modules/nativoBidAdapter.js' describe('nativoBidAdapterTests', function () { describe('isBidRequestValid', function () { let bid = { - bidder: 'nativo', - params: { - placementId: '10433394', - }, - adUnitCode: 'adunit-code', - sizes: [ - [300, 250], - [300, 600], - ], - bidId: '27b02036ccfa6e', - bidderRequestId: '1372cd8bd8d6a8', - auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + bidder: 'nativo' } - it('should return true when required params found', function () { + it('should return true if no params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true for valid placementId value', function () { + bid.params = { + placementId: '10433394', + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true for valid placementId value', function () { + bid.params = { + placementId: 10433394, + } expect(spec.isBidRequestValid(bid)).to.equal(true) }) - it('should return false when required params are not passed', function () { - let bid2 = Object.assign({}, bid) - delete bid2.params - bid2.params = {} - expect(spec.isBidRequestValid(bid2)).to.equal(false) + it('should return false for invalid placementId value', function () { + bid.params = { + placementId: true, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should return true for valid placementId value', function () { + bid.params = { + url: 'www.test.com', + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return false for invalid placementId value', function () { + bid.params = { + url: 4567890, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) }) }) @@ -65,8 +82,11 @@ describe('nativoBidAdapterTests', function () { expect(request.url).to.be.a('string') expect(request.url).to.include('?') - expect(request.url).to.include('ntv_url') expect(request.url).to.include('ntv_ptd') + expect(request.url).to.include('ntv_pb_rid') + expect(request.url).to.include('ntv_ppc') + expect(request.url).to.include('ntv_url') + expect(request.url).to.include('ntv_dbr') }) }) }) @@ -119,14 +139,19 @@ describe('interpretResponse', function () { bids: [ { params: { - placementId: 1 - } + placementId: 1, + }, }, ], } // mock - spec.getRequestId = () => 123456 + spec.getAdUnitData = () => { + return { + bidId: 123456, + sizes: [300, 250], + } + } let result = spec.interpretResponse({ body: response }, { bidderRequest }) expect(Object.keys(result[0])).to.have.deep.members( @@ -171,11 +196,11 @@ describe('getUserSyncs', function () { const gdprConsent = { gdprApplies: true, - consentString: '111111' + consentString: '111111', } const uspConsent = { - uspConsent: '1YYY' + uspConsent: '1YYY', } it('Returns empty array if no supported user syncs', function () { @@ -205,7 +230,9 @@ describe('getUserSyncs', function () { expect(userSync[0].type).to.exist expect(userSync[0].url).to.exist expect(userSync[0].type).to.be.equal('iframe') - expect(userSync[0].url).to.contain('gdpr=1&gdpr_consent=111111&us_privacy=1YYY') + expect(userSync[0].url).to.contain( + 'gdpr=1&gdpr_consent=111111&us_privacy=1YYY' + ) }) it('Returns valid URL and type', function () { @@ -222,6 +249,50 @@ describe('getUserSyncs', function () { expect(userSync[0].type).to.exist expect(userSync[0].url).to.exist expect(userSync[0].type).to.be.equal('image') - expect(userSync[0].url).to.contain('gdpr=1&gdpr_consent=111111&us_privacy=1YYY') + expect(userSync[0].url).to.contain( + 'gdpr=1&gdpr_consent=111111&us_privacy=1YYY' + ) + }) +}) + +describe('getAdUnitData', () => { + afterEach(() => { + if (window.bidRequestMap) delete window.bidRequestMap + }) + + it('Matches placementId value', () => { + const adUnitData = { + bidId: 123456, + sizes: [300, 250], + } + + window.bidRequestMap = { + 9876543: { + 12345: adUnitData, + }, + } + + const data = spec.getAdUnitData(9876543, { impid: 12345 }) + expect(Object.keys(data)).to.have.deep.members( + Object.keys(adUnitData) + ) + }) + + it('Falls back to ad unit code value', () => { + const adUnitData = { + bidId: 123456, + sizes: [300, 250], + } + + window.bidRequestMap = { + 9876543: { + '#test-code': adUnitData, + }, + } + + const data = spec.getAdUnitData(9876543, { impid: 12345, ext: { ad_unit_code: '#test-code' } }) + expect(Object.keys(data)).to.have.deep.members( + Object.keys(adUnitData) + ) }) }) diff --git a/test/spec/modules/naveggIdSystem_spec.js b/test/spec/modules/naveggIdSystem_spec.js new file mode 100644 index 00000000000..c0973a05372 --- /dev/null +++ b/test/spec/modules/naveggIdSystem_spec.js @@ -0,0 +1,21 @@ +import { naveggIdSubmodule, storage } from 'modules/naveggIdSystem.js'; + +describe('naveggId', function () { + it('should NOT find navegg id', function () { + let id = naveggIdSubmodule.getId(); + + expect(id).to.be.undefined; + }); + + it('getId() should return "test-nid" id from cookie OLD_NAVEGG_ID', function() { + sinon.stub(storage, 'getCookie').withArgs('nid').returns('test-nid'); + let id = naveggIdSubmodule.getId(); + expect(id).to.be.deep.equal({id: 'test-nid'}) + }) + + it('getId() should return "test-nvggid" id from local storage NAVEGG_ID', function() { + sinon.stub(storage, 'getDataFromLocalStorage').withArgs('nvggid').returns('test-ninvggidd'); + let id = naveggIdSubmodule.getId(); + expect(id).to.be.deep.equal({id: 'test-ninvggidd'}) + }) +}); diff --git a/test/spec/modules/newborntownWebBidAdapter_spec.js b/test/spec/modules/newborntownWebBidAdapter_spec.js deleted file mode 100644 index 3d3285328fe..00000000000 --- a/test/spec/modules/newborntownWebBidAdapter_spec.js +++ /dev/null @@ -1,152 +0,0 @@ -import { expect } from 'chai'; -import {spec} from 'modules/newborntownWebBidAdapter.js'; -describe('NewborntownWebAdapter', function() { - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'newborntownWeb', - 'params': { - 'publisher_id': '1238122', - 'slot_id': '123123', - 'bidfloor': 0.3 - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'sizes': [[300, 250]], - 'bidId': '2e9cf65f23dbd9', - 'bidderRequestId': '1f01d9d22ee657', - 'auctionId': '2bf455a4-a889-41d5-b48f-9b56b89fbec7', - } - it('should return true where required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }) - describe('buildRequests', function () { - let bidderRequest = { - 'bidderCode': 'newborntownWeb', - 'bidderRequestId': '1f5c279a4c5de3', - 'bids': [ - { - 'bidder': 'newborntownWeb', - 'params': { - 'publisher_id': '1238122', - 'slot_id': '123123', - 'bidfloor': 0.3 - }, - 'mediaTypes': { - 'banner': {'sizes': [[300, 250]]} - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': '9b954797-d6f4-4730-9cbe-5a1bc8480f52', - 'sizes': [[300, 250]], - 'bidId': '215f48d07eb8b8', - 'bidderRequestId': '1f5c279a4c5de3', - 'auctionId': '5ed4f607-e11c-45b0-aba9-f67768e1f9f4', - 'src': 'client', - 'bidRequestsCount': 1 - } - ], - 'auctionStart': 1573123289380, - 'timeout': 9000, - 'start': 1573123289383 - } - it('Returns POST method', function () { - const request = spec.buildRequests(bidderRequest['bids'], bidderRequest); - expect(request[0].method).to.equal('POST'); - expect(request[0].url.indexOf('//us-west.solortb.com/adx/api/rtb?from=4') !== -1).to.equal(true); - expect(request[0].data).to.exist; - }); - it('request params multi size format object check', function () { - let bidderRequest = { - 'bidderCode': 'newborntownWeb', - 'bidderRequestId': '1f5c279a4c5de3', - 'bids': [ - { - 'bidder': 'newborntownWeb', - 'params': { - 'publisher_id': '1238122', - 'slot_id': '123123', - 'bidfloor': 0.3 - }, - 'mediaTypes': { - 'native': {'sizes': [[300, 250]]} - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': '9b954797-d6f4-4730-9cbe-5a1bc8480f52', - 'sizes': [300, 250], - 'bidId': '215f48d07eb8b8', - 'bidderRequestId': '1f5c279a4c5de3', - 'auctionId': '5ed4f607-e11c-45b0-aba9-f67768e1f9f4', - 'src': 'client', - 'bidRequestsCount': 1 - } - ], - 'auctionStart': 1573123289380, - 'timeout': 9000, - 'start': 1573123289383 - } - let requstTest = spec.buildRequests(bidderRequest['bids'], bidderRequest) - expect(requstTest[0].data.imp[0].banner.w).to.equal(300); - expect(requstTest[0].data.imp[0].banner.h).to.equal(250); - }); - }) - describe('interpretResponse', function () { - let serverResponse; - let bidRequest = { - data: { - bidId: '2d359291dcf53b' - } - }; - beforeEach(function () { - serverResponse = { - 'body': { - 'id': '174548259807190369860081', - 'seatbid': [ - { - 'bid': [ - { - 'id': '1573540665390298996', - 'impid': '1', - 'price': 0.3001, - 'adid': '1573540665390299172', - 'nurl': 'https://us-west.solortb.com/winnotice?price=${AUCTION_PRICE}&ssp=4&req_unique_id=740016d1-175b-4c19-9744-58a59632dabe&unique_id=06b08e40-2489-439a-8f9e-6413f3dd0bc8&isbidder=1&up=bQyvVo7tgbBVW2dDXzTdBP95Mv35YqqEika0T_btI1h6xjqA8GSXQe51_2CCHQcfuwAEOgdwN8u3VgUHmCuqNPKiBmIPaYUOQBBKjJr05zeKtabKnGT7_JJKcurrXqQ5Sl804xJear_qf2-jOaKB4w', - 'adm': "
", - 'adomain': [ - 'newborntown.com' - ], - 'iurl': 'https://sdkvideo.s3.amazonaws.com/4aa1d9533c4ce71bb1cf750ed38e3a58.png', - 'cid': '345', - 'crid': '41_11113', - 'cat': [ - '' - ], - 'h': 250, - 'w': 300 - } - ], - 'seat': '1' - } - ], - 'bidid': 'bid1573540665390298585' - }, - 'headers': { - - } - } - }); - it('result is correct', function () { - const result = spec.interpretResponse(serverResponse, bidRequest); - if (result && result[0]) { - expect(result[0].requestId).to.equal('2d359291dcf53b'); - expect(result[0].cpm).to.equal(0.3001); - expect(result[0].width).to.equal(300); - expect(result[0].height).to.equal(250); - expect(result[0].creativeId).to.equal('345'); - } - }); - }) -}) diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index f4d929b439c..0dfee96d0ea 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -2,90 +2,92 @@ import { expect } from 'chai'; import { spec } from 'modules/nextMillenniumBidAdapter.js'; describe('nextMillenniumBidAdapterTests', function() { - let bidRequestData = { - bids: [ - { - bidId: 'transaction_1234', - bidder: 'nextMillennium', - params: { - placement_id: 12345 - }, - sizes: [[300, 250]] + const bidRequestData = [ + { + adUnitCode: 'test-div', + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { placement_id: '-1' }, + sizes: [[300, 250]], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true } - ] - }; - let request = []; + } + ]; - it('validate_pub_params', function() { - expect( - spec.isBidRequestValid({ - bidder: 'nextMillennium', - params: { - placement_id: 12345 - } - }) - ).to.equal(true); + it('Request params check with GDPR and USP Consent', function () { + const request = spec.buildRequests(bidRequestData, bidRequestData[0]); + expect(JSON.parse(request[0].data).user.ext.consent).to.equal('kjfdniwjnifwenrif3'); + expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('1---'); + expect(JSON.parse(request[0].data).regs.ext.gdpr).to.equal(1); + }); + + it('Request params check without GDPR Consent', function () { + delete bidRequestData[0].gdprConsent + const request = spec.buildRequests(bidRequestData, bidRequestData[0]); + expect(JSON.parse(request[0].data).regs.ext.gdpr).to.be.undefined; + expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('1---'); }); it('validate_generated_params', function() { - let bidRequestData = [ - { - bidId: 'bid1234', - bidder: 'nextMillennium', - params: { placement_id: -1 }, - sizes: [[300, 250]] - } - ]; - let request = spec.buildRequests(bidRequestData); + const request = spec.buildRequests(bidRequestData); expect(request[0].bidId).to.equal('bid1234'); + expect(JSON.parse(request[0].data).id).to.equal('b06c5141-fe8f-4cdf-9d7d-54415490a917'); }); - it('validate_getUserSyncs_function', function() { - expect(spec.getUserSyncs({ iframeEnabled: true })).to.have.lengthOf(1); - expect(spec.getUserSyncs({ iframeEnabled: false })).to.have.lengthOf(0); + it('Check if refresh_count param is incremented', function() { + const request = spec.buildRequests(bidRequestData); + expect(JSON.parse(request[0].data).ext.nextMillennium.refresh_count).to.equal(3); + }); - let pixel = spec.getUserSyncs({ iframeEnabled: true }); - expect(pixel[0].type).to.equal('iframe'); - expect(pixel[0].url).to.equal('https://brainlyads.com/hb/s2s/matching'); + it('Test getUserSyncs function', function () { + const syncOptions = { + 'iframeEnabled': true + } + const userSync = spec.getUserSyncs(syncOptions); + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[0].url).to.be.equal('https://statics.nextmillmedia.com/load-cookie.html?v=4'); }); it('validate_response_params', function() { - let serverResponse = { + const serverResponse = { body: { - cpm: 1.7, - width: 300, - height: 250, - creativeId: 'p35t0enob6twbt9mofjc8e', - ad: 'Hello! It\'s a test ad!' + id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', + seatbid: [ + { + bid: [ + { + id: '7457329903666272789', + price: 0.5, + adm: 'Hello! It\'s a test ad!', + adid: '96846035', + adomain: ['test.addomain.com'], + w: 300, + h: 250 + } + ] + } + ], + cur: 'USD' } }; - let bids = spec.interpretResponse(serverResponse, bidRequestData.bids[0]); + let bids = spec.interpretResponse(serverResponse, bidRequestData[0]); expect(bids).to.have.lengthOf(1); let bid = bids[0]; - expect(bid.creativeId).to.equal('p35t0enob6twbt9mofjc8e'); + expect(bid.creativeId).to.equal('96846035'); expect(bid.ad).to.equal('Hello! It\'s a test ad!'); - expect(bid.cpm).to.equal(1.7); + expect(bid.cpm).to.equal(0.5); expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); expect(bid.currency).to.equal('USD'); }); - - it('validate_response_params_with passback', function() { - let serverResponse = { - body: [ - { - hash: '1e100887dd614b0909bf6c49ba7f69fdd1360437', - content: 'Ad html passback', - size: [300, 250], - is_passback: 1 - } - ] - }; - let bids = spec.interpretResponse(serverResponse); - - expect(bids).to.have.lengthOf(0); - }); }); diff --git a/test/spec/modules/nextrollBidAdapter_spec.js b/test/spec/modules/nextrollBidAdapter_spec.js index 7722443e584..4699fbc6e08 100644 --- a/test/spec/modules/nextrollBidAdapter_spec.js +++ b/test/spec/modules/nextrollBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { spec, tryGetPubtag, hasCCPAConsent } from 'modules/nextrollBidAdapter.js'; +import { spec } from 'modules/nextrollBidAdapter.js'; import * as utils from 'src/utils.js'; +import { deepClone } from '../../../src/utils'; describe('nextrollBidAdapter', function() { let utilsMock; @@ -116,6 +117,27 @@ describe('nextrollBidAdapter', function() { expect(request.data.imp.ext.zone.id).to.be.equal('zone1'); }); + it('builds a request with the correct floor object', function () { + // bidfloor is defined, getFloor isn't + let bid = deepClone(validBid); + let request = spec.buildRequests([bid], {})[0]; + expect(request.data.imp.bidfloor).to.be.equal(1); + + // bidfloor not defined, getFloor not defined + bid = deepClone(validBid); + bid.params.bidfloor = null; + request = spec.buildRequests([bid], {})[0]; + expect(request.data.imp.bidfloor).to.not.exist; + + // bidfloor defined, getFloor defined, use getFloor + let getFloorResponse = { currency: 'USD', floor: 3 }; + bid = deepClone(validBid); + bid.getFloor = () => getFloorResponse; + request = spec.buildRequests([bid], {})[0]; + + expect(request.data.imp.bidfloor).to.exist.and.to.equal(3); + }); + it('includes the sizes into the request correctly', function () { const bannerObject = spec.buildRequests([validBid], {})[0].data.imp.banner; diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js new file mode 100644 index 00000000000..7501391cbfc --- /dev/null +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -0,0 +1,140 @@ +import {expect} from 'chai'; +import {spec} from 'modules/nexx360BidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import { requestBidsHook } from 'modules/consentManagement.js'; + +describe('Nexx360 bid adapter tests', function () { + var DEFAULT_PARAMS = [{ + adUnitCode: 'banner-div', + bidId: 'aaaa1234', + auctionId: 'bbbb1234', + transactionId: 'cccc1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bidder: 'nexx360', + params: { + account: '1067', + tagId: 'luvxjvgn' + }, + }]; + + var BID_RESPONSE = {'body': { + 'responses': [ + { + 'bidId': '49a705a42610a', + 'cpm': 0.437245, + 'width': 300, + 'height': 250, + 'creativeId': '98493581', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + 'uuid': 'ce6d1ee3-2a05-4d7c-b97a-9e62097798ec', + 'bidder': 'appnexus', + 'consent': 1, + 'tagId': 'luvxjvgn' + } + ], + }}; + + const DEFAULT_OPTIONS = { + gdprConsent: { + gdprApplies: true, + consentString: 'BOzZdA0OzZdA0AGABBENDJ-AAAAvh7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__79__3z3_9pxP78k89r7337Mw_v-_v-b7JCPN_Y3v-8Kg', + vendorData: {} + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + }, + uspConsent: '111222333', + userId: { 'id5id': { uid: '1111' } }, + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + }] + }, + }; + it('Verify build request', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + expect(request).to.have.property('url').and.to.equal('https://fast.nexx360.io/prebid'); + expect(request).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request.data); + expect(requestContent.adUnits[0]).to.have.property('account').and.to.equal('1067'); + expect(requestContent.adUnits[0]).to.have.property('tagId').and.to.equal('luvxjvgn'); + expect(requestContent.adUnits[0]).to.have.property('label').and.to.equal('banner-div'); + expect(requestContent.adUnits[0]).to.have.property('bidId').and.to.equal('aaaa1234'); + expect(requestContent.adUnits[0]).to.have.property('auctionId').and.to.equal('bbbb1234'); + expect(requestContent.adUnits[0]).to.have.property('mediatypes').exist; + }); + + it('Verify parse response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + const response = spec.interpretResponse(BID_RESPONSE, request); + expect(response).to.have.lengthOf(1); + const bid = response[0]; + expect(bid.cpm).to.equal(0.437245); + expect(bid.adUrl).to.equal('https://fast.nexx360.io/cache?uuid=ce6d1ee3-2a05-4d7c-b97a-9e62097798ec'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('98493581'); + expect(bid.currency).to.equal('EUR'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(360); + expect(bid.requestId).to.equal('49a705a42610a'); + expect(bid.nexx360).to.exist; + expect(bid.nexx360.ssp).to.equal('appnexus'); + }); + it('Verifies bidder code', function () { + expect(spec.code).to.equal('nexx360'); + }); + + it('Verifies bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('revenuemaker'); + }); + it('Verifies if bid request valid', function () { + expect(spec.isBidRequestValid(DEFAULT_PARAMS[0])).to.equal(true); + }); + it('Verifies bid won', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + const response = spec.interpretResponse(BID_RESPONSE, request); + const won = spec.onBidWon(response[0]); + expect(won).to.equal(true); + }); + it('Verifies user sync without cookie in bid response', function () { + var syncs = spec.getUserSyncs({}, [BID_RESPONSE], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.lengthOf(0); + }); + it('Verifies user sync with cookies in bid response', function () { + BID_RESPONSE.body.cookies = [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}]; + var syncs = spec.getUserSyncs({}, [BID_RESPONSE], DEFAULT_OPTIONS.gdprConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0]).to.have.property('type').and.to.equal('image'); + expect(syncs[0]).to.have.property('url').and.to.equal('http://www.cookie.sync.org/'); + }); + it('Verifies user sync with no bid response', function() { + var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.lengthOf(0); + }); + it('Verifies user sync with no bid body response', function() { + var syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.lengthOf(0); + var syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.lengthOf(0); + }); +}); diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index 8dc15fbc89e..eccf0e84031 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import * as utils from 'src/utils.js'; import { spec } from 'modules/nobidBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; import * as bidderFactory from 'src/adapters/bidderFactory.js'; describe('Nobid Adapter', function () { @@ -50,6 +51,206 @@ describe('Nobid Adapter', function () { }); }); + describe('Request with ORTB2', function () { + const SITE_ID = 2; + const REFERER = 'https://www.examplereferer.com'; + const BIDDER_CODE = 'duration'; + let bidRequests = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'siteId': SITE_ID + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + let bidderRequest = { + refererInfo: {referer: REFERER}, bidderCode: BIDDER_CODE + } + + const siteName = 'example'; + const siteDomain = 'page.example.com'; + const sitePage = 'https://page.example.com/here.html'; + const siteRef = 'https://ref.example.com'; + const siteKeywords = 'power tools, drills'; + const siteSearch = 'drill'; + const siteCat = 'IAB2'; + const siteSectionCat = 'IAB2-2'; + const sitePageCat = 'IAB2-12'; + + it('ortb2 should exist', function () { + config.setConfig({ + ortb2: { + site: { + name: siteName, + domain: siteDomain, + cat: [ siteCat ], + sectioncat: [ siteSectionCat ], + pagecat: [ sitePageCat ], + page: sitePage, + ref: siteRef, + keywords: siteKeywords, + search: siteSearch + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + let payload = JSON.parse(request.data); + payload = JSON.parse(JSON.stringify(payload)); + expect(payload.sid).to.equal(SITE_ID); + expect(payload.ortb2.site.name).to.equal(siteName); + expect(payload.ortb2.site.domain).to.equal(siteDomain); + expect(payload.ortb2.site.page).to.equal(sitePage); + expect(payload.ortb2.site.ref).to.equal(siteRef); + expect(payload.ortb2.site.keywords).to.equal(siteKeywords); + expect(payload.ortb2.site.search).to.equal(siteSearch); + expect(payload.ortb2.site.cat[0]).to.equal(siteCat); + expect(payload.ortb2.site.sectioncat[0]).to.equal(siteSectionCat); + expect(payload.ortb2.site.pagecat[0]).to.equal(sitePageCat); + }); + }); + + describe('isDurationBidRequestValid', function () { + const SITE_ID = 2; + const REFERER = 'https://www.examplereferer.com'; + const BIDDER_CODE = 'duration'; + let bidRequests = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'siteId': SITE_ID + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + let bidderRequest = { + refererInfo: {referer: REFERER}, bidderCode: BIDDER_CODE + } + + it('should add source and version to the tag', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.sid).to.equal(SITE_ID); + expect(payload.pjbdr).to.equal(BIDDER_CODE); + expect(payload.l).to.exist.and.to.equal(encodeURIComponent(REFERER)); + expect(payload.tt).to.exist; + expect(payload.a).to.exist; + expect(payload.t).to.exist; + expect(payload.tz).to.exist; + expect(payload.r).to.exist; + expect(payload.lang).to.exist; + expect(payload.ref).to.exist; + expect(payload.gdpr).to.exist; + }); + + it('sends bid request to ad size', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.a).to.exist; + expect(payload.a.length).to.exist.and.to.equal(1); + expect(payload.a[0].z[0][0]).to.equal(300); + expect(payload.a[0].z[0][1]).to.equal(250); + }); + + it('sends bid request to div id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.a).to.exist; + expect(payload.a[0].d).to.equal('adunit-code'); + }); + + it('sends bid request to site id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.a).to.exist; + expect(payload.a[0].sid).to.equal(2); + expect(payload.a[0].at).to.equal('banner'); + expect(payload.a[0].params.siteId).to.equal(2); + }); + + it('sends bid request to ad type', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.a).to.exist; + expect(payload.a[0].at).to.equal('banner'); + }); + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.contain('ads.servenobid.com/adreq'); + expect(request.method).to.equal('POST'); + }); + + it('should add gdpr consent information to the request', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'nobid', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consentString).to.exist.and.to.equal(consentString); + expect(payload.gdpr.consentRequired).to.exist.and.to.be.true; + }); + + it('should add gdpr consent information to the request', function () { + let bidderRequest = { + 'bidderCode': 'nobid', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + gdprApplies: false + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consentString).to.not.exist; + expect(payload.gdpr.consentRequired).to.exist.and.to.be.false; + }); + + it('should add usp consent information to the request', function () { + let bidderRequest = { + 'bidderCode': 'nobid', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': '1Y-N' + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.usp).to.exist; + expect(payload.usp).to.exist.and.to.equal('1Y-N'); + }); + }); + describe('isVideoBidRequestValid', function () { let bid = { bidder: 'nobid', @@ -114,11 +315,11 @@ describe('Nobid Adapter', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); expect(payload.sid).to.equal(SITE_ID); + expect(payload.pjbdr).to.equal('nobid'); expect(payload.l).to.exist.and.to.equal(encodeURIComponent(REFERER)); expect(payload.a).to.exist; expect(payload.t).to.exist; expect(payload.tz).to.exist; - expect(payload.r).to.exist.and.to.equal('100x100'); expect(payload.lang).to.exist; expect(payload.ref).to.exist; expect(payload.a[0].d).to.exist.and.to.equal('adunit-code'); @@ -202,6 +403,7 @@ describe('Nobid Adapter', function () { it('should add source and version to the tag', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); + expect(payload.pjbdr).to.equal('nobid'); expect(payload.sid).to.equal(SITE_ID); expect(payload.l).to.exist.and.to.equal(encodeURIComponent(REFERER)); expect(payload.a).to.exist; @@ -257,12 +459,12 @@ describe('Nobid Adapter', function () { 'uids': [ { 'id': 'ID5_ID', - 'atype': 1, - 'ext': { - 'linkType': 0 - } + 'atype': 1 } - ] + ], + 'ext': { + 'linkType': 0 + } }, { 'source': 'adserver.org', @@ -284,7 +486,7 @@ describe('Nobid Adapter', function () { refererInfo: {referer: REFERER} } - it('should get user ids from eids', function () { + it('should criteo eid', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); expect(payload.sid).to.exist.and.to.equal(2); @@ -598,6 +800,47 @@ describe('Nobid Adapter', function () { }); }); + describe('interpretResponseWithMeta', function () { + const CREATIVE_ID_300x250 = 'CREATIVE-100'; + const ADUNIT_300x250 = 'ADUNIT-1'; + const ADMARKUP_300x250 = 'ADMARKUP-300x250'; + const PRICE_300x250 = 0.51; + const REQUEST_ID = '3db3773286ee59'; + const DEAL_ID = 'deal123'; + const ADOMAINS = ['adomain1', 'adomain2']; + let response = { + country: 'US', + ip: '68.83.15.75', + device: 'COMPUTER', + site: 2, + bids: [ + {id: 1, + bdrid: 101, + divid: ADUNIT_300x250, + dealid: DEAL_ID, + creativeid: CREATIVE_ID_300x250, + size: {'w': 300, 'h': 250}, + adm: ADMARKUP_300x250, + price: '' + PRICE_300x250, + meta: { + advertiserDomains: ADOMAINS + } + } + ] + }; + + it('should meta.advertiserDomains be respected', function () { + let bidderRequest = { + bids: [{ + bidId: REQUEST_ID, + adUnitCode: ADUNIT_300x250 + }] + } + let result = spec.interpretResponse({ body: response }, {bidderRequest: bidderRequest}); + expect(result[0].meta.advertiserDomains).to.equal(ADOMAINS); + }); + }); + describe('buildRequestsWithSupplyChain', function () { const SITE_ID = 2; let bidRequests = [ diff --git a/test/spec/modules/novatiqIdSystem_spec.js b/test/spec/modules/novatiqIdSystem_spec.js index 60c82626450..1805d6791cf 100644 --- a/test/spec/modules/novatiqIdSystem_spec.js +++ b/test/spec/modules/novatiqIdSystem_spec.js @@ -52,6 +52,24 @@ describe('novatiqIdSystem', function () { const response = novatiqIdSubmodule.getId(config); expect(response.id).should.be.not.empty; }); + + it('should set sharedStatus if sharedID is configured but returned null', function() { + const config = { params: { sourceid: '123', useSharedId: true } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.sharedStatus).to.equal('Not Found'); + }); + + it('should set sharedStatus if sharedID is configured and is valid', function() { + const config = { params: { sourceid: '123', useSharedId: true } }; + + let stub = sinon.stub(novatiqIdSubmodule, 'getSharedId').returns('fakeId'); + + const response = novatiqIdSubmodule.getId(config); + + stub.restore(); + + expect(response.sharedStatus).to.equal('Found'); + }); }); describe('decode', function() { diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js new file mode 100644 index 00000000000..acf62bf5a7b --- /dev/null +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -0,0 +1,632 @@ +import { expect } from 'chai'; +import { spec } from 'modules/oguryBidAdapter'; +import * as utils from 'src/utils.js'; + +const BID_URL = 'https://mweb-hb.presage.io/api/header-bidding-request'; +const TIMEOUT_URL = 'https://ms-ads-monitoring-events.presage.io/bid_timeout' + +describe('OguryBidAdapter', function () { + let bidRequests; + let bidderRequest; + + bidRequests = [ + { + adUnitCode: 'adUnitCode', + auctionId: 'auctionId', + bidId: 'bidId', + bidder: 'ogury', + params: { + assetKey: 'OGY-assetkey', + adUnitId: 'adunitId', + xMargin: 20, + yMarging: 20, + gravity: 'TOP_LEFT', + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + getFloor: ({ size, currency, mediaType }) => { + const floorResult = { + currency: 'USD', + floor: 0 + }; + + if (mediaType === 'banner') { + floorResult.floor = 4; + } else { + floorResult.floor = 1000; + } + + return floorResult; + }, + transactionId: 'transactionId' + }, + { + adUnitCode: 'adUnitCode2', + auctionId: 'auctionId', + bidId: 'bidId2', + bidder: 'ogury', + params: { + assetKey: 'OGY-assetkey', + adUnitId: 'adunitId2' + }, + mediaTypes: { + banner: { + sizes: [[600, 500]] + } + }, + transactionId: 'transactionId2' + }, + ]; + + bidderRequest = { + auctionId: bidRequests[0].auctionId, + gdprConsent: {consentString: 'myConsentString', vendorData: {}, gdprApplies: true}, + }; + + describe('isBidRequestValid', function () { + it('should validate correct bid', () => { + let validBid = utils.deepClone(bidRequests[0]); + + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('should not validate incorrect bid', () => { + let invalidBid = utils.deepClone(bidRequests[0]); + delete invalidBid.sizes; + delete invalidBid.mediaTypes; + + let isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.equal(false); + }); + + it('should not validate bid if adunit is not present', () => { + let invalidBid = utils.deepClone(bidRequests[0]); + delete invalidBid.params.adUnitId; + + let isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.equal(false); + }); + + it('should not validate bid if assetKet is not present', () => { + let invalidBid = utils.deepClone(bidRequests[0]); + delete invalidBid.params.assetKey; + + let isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.equal(false); + }); + + it('should validate bid if getFloor is not present', () => { + let invalidBid = utils.deepClone(bidRequests[1]); + delete invalidBid.getFloor; + + let isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.equal(true); + }); + }); + + describe('getUserSyncs', function() { + let syncOptions, gdprConsent; + + beforeEach(() => { + syncOptions = {pixelEnabled: true}; + gdprConsent = { + gdprApplies: true, + consentString: 'CPJl4C8PJl4C8OoAAAENAwCMAP_AAH_AAAAAAPgAAAAIAPgAAAAIAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g' + }; + }); + + it('should return syncs array with two elements of type image', () => { + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + + expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[0].url).to.contain('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch'); + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[1].url).to.contain('https://ms-cookie-sync.presage.io/ttd/init-sync'); + }); + + it('should set the source as query param', () => { + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(userSyncs[0].url).to.contain('source=prebid'); + expect(userSyncs[1].url).to.contain('source=prebid'); + }); + + it('should set the tcString as query param', () => { + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(userSyncs[0].url).to.contain(`iab_string=${gdprConsent.consentString}`); + expect(userSyncs[1].url).to.contain(`iab_string=${gdprConsent.consentString}`); + }); + + it('should return an empty array when pixel is disable', () => { + syncOptions.pixelEnabled = false; + expect(spec.getUserSyncs(syncOptions, [], gdprConsent)).to.have.lengthOf(0); + }); + + it('should return syncs array with two elements of type image when consentString is undefined', () => { + gdprConsent = { + gdprApplies: true, + consentString: undefined + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + }); + + it('should return syncs array with two elements of type image when consentString is null', () => { + gdprConsent = { + gdprApplies: true, + consentString: null + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + }); + + it('should return syncs array with two elements of type image when gdprConsent is undefined', () => { + gdprConsent = undefined; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + }); + + it('should return syncs array with two elements of type image when gdprConsent is null', () => { + gdprConsent = null; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + }); + + it('should return syncs array with two elements of type image when gdprConsent is null and gdprApplies is false', () => { + gdprConsent = { + gdprApplies: false, + consentString: null + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + }); + + it('should return syncs array with two elements of type image when gdprConsent is empty string and gdprApplies is false', () => { + gdprConsent = { + gdprApplies: false, + consentString: '' + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + }); + }); + + describe('buildRequests', function () { + const defaultTimeout = 1000; + const expectedRequestObject = { + id: bidRequests[0].auctionId, + at: 1, + tmax: defaultTimeout, + imp: [{ + id: bidRequests[0].bidId, + tagid: bidRequests[0].params.adUnitId, + bidfloor: 4, + banner: { + format: [{ + w: 300, + h: 250 + }] + }, + ext: bidRequests[0].params + }, { + id: bidRequests[1].bidId, + tagid: bidRequests[1].params.adUnitId, + bidfloor: 0, + banner: { + format: [{ + w: 600, + h: 500 + }] + }, + ext: bidRequests[1].params + }], + regs: { + ext: { + gdpr: 1 + }, + }, + site: { + id: bidRequests[0].params.assetKey, + domain: window.location.hostname, + page: window.location.href + }, + user: { + ext: { + consent: bidderRequest.gdprConsent.consentString + }, + }, + ext: { + prebidversion: '$prebid.version$', + adapterversion: '1.2.10' + } + }; + + it('sends bid request to ENDPOINT via POST', function () { + const validBidRequests = utils.deepClone(bidRequests) + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.url).to.equal(BID_URL); + expect(request.method).to.equal('POST'); + }); + + it('bid request object should be conform', function () { + const validBidRequests = utils.deepClone(bidRequests) + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestObject); + expect(request.data.regs.ext.gdpr).to.be.a('number'); + }); + + it('should not add gdpr infos if not present', () => { + const bidderRequestWithoutGdpr = { + ...bidderRequest, + gdprConsent: {}, + } + const expectedRequestObjectWithoutGdpr = { + ...expectedRequestObject, + regs: { + ext: { + gdpr: 0 + }, + }, + user: { + ext: { + consent: '' + }, + } + }; + + const validBidRequests = bidRequests + + const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); + expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); + expect(request.data.regs.ext.gdpr).to.be.a('number'); + }); + + it('should not add gdpr infos if gdprConsent is undefined', () => { + const bidderRequestWithoutGdpr = { + ...bidderRequest, + gdprConsent: undefined, + } + const expectedRequestObjectWithoutGdpr = { + ...expectedRequestObject, + regs: { + ext: { + gdpr: 0 + }, + }, + user: { + ext: { + consent: '' + }, + } + }; + + const validBidRequests = bidRequests + + const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); + expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); + expect(request.data.regs.ext.gdpr).to.be.a('number'); + }); + + it('should not add tcString and turn off gdpr-applies if consentString and gdprApplies are undefined', () => { + const bidderRequestWithoutGdpr = { + ...bidderRequest, + gdprConsent: { consentString: undefined, gdprApplies: undefined }, + } + const expectedRequestObjectWithoutGdpr = { + ...expectedRequestObject, + regs: { + ext: { + gdpr: 0 + }, + }, + user: { + ext: { + consent: '' + }, + } + }; + + const validBidRequests = bidRequests + + const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); + expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); + expect(request.data.regs.ext.gdpr).to.be.a('number'); + }); + + it('should handle bidFloor undefined', () => { + const expectedRequestWithUndefinedFloor = { + ...expectedRequestObject + }; + + const validBidRequests = utils.deepClone(bidRequests); + validBidRequests[1] = { + ...validBidRequests[1], + getFloor: undefined + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestWithUndefinedFloor); + }); + + it('should handle bidFloor when is not function', () => { + const expectedRequestWithNotAFunctionFloor = { + ...expectedRequestObject + }; + + let validBidRequests = utils.deepClone(bidRequests); + validBidRequests[1] = { + ...validBidRequests[1], + getFloor: 'getFloor' + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestWithNotAFunctionFloor); + }); + + it('should handle bidFloor when currency is not USD', () => { + const expectedRequestWithUnsupportedFloorCurrency = utils.deepClone(expectedRequestObject) + expectedRequestWithUnsupportedFloorCurrency.imp[0].bidfloor = 0; + let validBidRequests = utils.deepClone(bidRequests); + validBidRequests[0] = { + ...validBidRequests[0], + getFloor: ({ size, currency, mediaType }) => { + return { + currency: 'EUR', + floor: 4 + } + } + }; + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestWithUnsupportedFloorCurrency); + }); + }); + + describe('interpretResponse', function () { + let openRtbBidResponse = { + body: { + id: 'id_of_bid_response', + seatbid: [{ + bid: [{ + id: 'advertId', + impid: 'bidId', + price: 100, + nurl: 'url', + adm: `test creative
cookies
`, + adomain: ['renault.fr'], + ext: { + adcontent: 'sample_creative', + advertid: '1a278c48-b79a-4bbf-b69f-3824803e7d87', + campaignid: '31724', + mediatype: 'image', + userid: 'ab4aabed-5230-49d9-9f1a-f06280d28366', + usersync: true, + advertiserid: '1', + isomidcompliant: false + }, + w: 180, + h: 101 + }, { + id: 'advertId2', + impid: 'bidId2', + price: 150, + nurl: 'url2', + adm: `test creative
cookies
`, + adomain: ['peugeot.fr'], + ext: { + adcontent: 'sample_creative', + advertid: '2a278c48-b79a-4bbf-b69f-3824803e7d87', + campaignid: '41724', + userid: 'bb4aabed-5230-49d9-9f1a-f06280d28366', + usersync: false, + advertiserid: '2', + isomidcompliant: true, + mediatype: 'image', + landingpageurl: 'https://ogury.com' + }, + w: 600, + h: 500 + }], + }] + } + }; + + it('should correctly interpret bidResponse', () => { + let expectedInterpretedBidResponse = [{ + requestId: openRtbBidResponse.body.seatbid[0].bid[0].impid, + cpm: openRtbBidResponse.body.seatbid[0].bid[0].price, + currency: 'USD', + width: openRtbBidResponse.body.seatbid[0].bid[0].w, + height: openRtbBidResponse.body.seatbid[0].bid[0].h, + ad: openRtbBidResponse.body.seatbid[0].bid[0].adm, + ttl: 60, + ext: openRtbBidResponse.body.seatbid[0].bid[0].ext, + creativeId: openRtbBidResponse.body.seatbid[0].bid[0].id, + netRevenue: true, + meta: { + advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain + }, + nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, + adapterVersion: '1.2.10', + prebidVersion: '$prebid.version$' + }, { + requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, + cpm: openRtbBidResponse.body.seatbid[0].bid[1].price, + currency: 'USD', + width: openRtbBidResponse.body.seatbid[0].bid[1].w, + height: openRtbBidResponse.body.seatbid[0].bid[1].h, + ad: openRtbBidResponse.body.seatbid[0].bid[1].adm, + ttl: 60, + ext: openRtbBidResponse.body.seatbid[0].bid[1].ext, + creativeId: openRtbBidResponse.body.seatbid[0].bid[1].id, + netRevenue: true, + meta: { + advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain + }, + nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, + adapterVersion: '1.2.10', + prebidVersion: '$prebid.version$' + }] + + let request = spec.buildRequests(bidRequests, bidderRequest); + let result = spec.interpretResponse(openRtbBidResponse, request); + + expect(result).to.deep.equal(expectedInterpretedBidResponse) + }); + + it('should return empty array if error during parsing', () => { + const wrongOpenRtbBidReponse = 'wrong data' + let request = spec.buildRequests(bidRequests, bidderRequest); + let result = spec.interpretResponse(wrongOpenRtbBidReponse, request); + + expect(result).to.be.instanceof(Array); + expect(result.length).to.equal(0) + }) + }); + + describe('onBidWon', function() { + const nurl = 'https://fakewinurl.test'; + let xhr; + let requests; + + beforeEach(function() { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = (xhr) => { + requests.push(xhr); + }; + }) + + afterEach(function() { + xhr.restore() + }) + + it('Should not create nurl request if bid is undefined', function() { + spec.onBidWon() + expect(requests.length).to.equal(0); + }) + + it('Should not create nurl request if bid does not contains nurl', function() { + spec.onBidWon({}) + expect(requests.length).to.equal(0); + }) + + it('Should not create nurl request if bid contains undefined nurl', function() { + spec.onBidWon({ nurl: undefined }) + expect(requests.length).to.equal(0); + }) + + it('Should create nurl request if bid nurl', function() { + spec.onBidWon({ nurl }) + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal(nurl); + expect(requests[0].method).to.equal('GET') + }) + + it('Should trigger getWindowContext method', function() { + const bidSample = { + id: 'advertId', + impid: 'bidId', + price: 100, + nurl: 'url', + adm: `test creative
cookies
`, + adomain: ['renault.fr'], + ext: { + adcontent: 'sample_creative', + advertid: '1a278c48-b79a-4bbf-b69f-3824803e7d87', + campaignid: '31724', + mediatype: 'image', + userid: 'ab4aabed-5230-49d9-9f1a-f06280d28366', + usersync: true, + advertiserid: '1', + isomidcompliant: false + }, + w: 180, + h: 101 + } + spec.onBidWon(bidSample) + expect(window.top.OG_PREBID_BID_OBJECT).to.deep.equal(bidSample) + }) + }) + + describe('getWindowContext', function() { + it('Should return top window if exist', function() { + const res = spec.getWindowContext() + expect(res).to.equal(window.top) + expect(res).to.not.be.undefined; + }) + + it('Should return self window if getting top window throw an error', function() { + const stub = sinon.stub(utils, 'getWindowTop') + stub.throws() + const res = spec.getWindowContext() + expect(res).to.equal(window.self) + utils.getWindowTop.restore() + }) + }) + + describe('onTimeout', function () { + let xhr; + let requests; + + beforeEach(function() { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = (xhr) => { + requests.push(xhr); + }; + }) + + afterEach(function() { + xhr.restore() + }) + + it('should send on bid timeout notification', function() { + const bid = { + ad: 'cookies', + cpm: 3 + } + spec.onTimeout(bid); + expect(requests).to.not.be.undefined; + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal(TIMEOUT_URL); + expect(requests[0].method).to.equal('POST'); + expect(JSON.parse(requests[0].requestBody).location).to.equal(window.location.href); + }) + }); +}); diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js index 3f5304dce0a..d6dacb44529 100644 --- a/test/spec/modules/oneVideoBidAdapter_spec.js +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -59,11 +59,77 @@ describe('OneVideoBidAdapter', function () { }); describe('spec.isBidRequestValid', function () { - it('should return true when the required params are passed', function () { + it('should return false when mediaTypes video OR banner not declared', function () { + bidRequest.mediaTypes = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return true (skip validations) when e2etest = true', function () { + bidRequest.params.video = { + e2etest: true + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when mediaTypes.video has all mandatory params', function () { + bidRequest.mediaTypes.video = { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + } + bidRequest.params.video = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when params.video has all override params instead of mediaTypes.video', function () { + bidRequest.mediaTypes.video = { + context: 'instream' + }; + bidRequest.params.video = { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'] + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when playerWidth & playerHeight are passed in params.video', function () { + bidRequest.mediaTypes.video = { + context: 'instream', + mimes: ['video/mp4', 'application/javascript'] + }; + bidRequest.params.video = { + playerWidth: 640, + playerHeight: 480, + }; expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }); - it('should return false when the "video" param is missing', function () { + it('should return true when mimes is passed in params.video', function () { + bidRequest.mediaTypes.video = { + context: 'instream', + playerSizes: [640, 480] + }; + bidRequest.video = { + mimes: ['video/mp4', 'application/javascript'] + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false when both mediaTypes.video and params.video Objects are missing', function () { + bidRequest.mediaTypes = {}; + bidRequest.params = { + pubId: 'brxd' + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when both mediaTypes.video and params.video are missing mimes and player size', function () { + bidRequest.mediaTypes = { + video: { + context: 'instream' + } + }; bidRequest.params = { pubId: 'brxd' }; @@ -76,34 +142,16 @@ describe('OneVideoBidAdapter', function () { playerWidth: 480, playerHeight: 640, mimes: ['video/mp4', 'application/javascript'], - protocols: [2, 5], - api: [2], - position: 1, - delivery: [2], - playbackmethod: [1, 5], - sid: 134, - rewarded: 1, - placement: 1, - inventoryid: 123 } }; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); + it('should return true when the "pubId" param exists', function () { - bidRequest.params = { + bidRequest.mediaTypes = { video: { - playerWidth: 480, - playerHeight: 640, - mimes: ['video/mp4', 'application/javascript'], - protocols: [2, 5], - api: [2], - position: 1, - delivery: [2], - playbackmethod: [1, 5], - sid: 134, - rewarded: 1, - placement: 1, - inventoryid: 123 + playerSizes: [640, 480], + mimes: ['video/mp4', 'application/javascript'] }, pubId: 'brxd' }; @@ -189,11 +237,27 @@ describe('OneVideoBidAdapter', function () { }, video: { context: 'outstream', - playerSize: [640, 480] + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'] } + }, + bidder: 'oneVideo', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + adUnitCode: 'video1', + params: { + video: { + protocols: [2, 5], + api: [2] + }, + site: { + page: 'https://news.yahoo.com/portfolios', + referrer: 'http://www.yahoo.com' + }, + pubId: 'brxd' } - } - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }) }); @@ -216,7 +280,7 @@ describe('OneVideoBidAdapter', function () { const placement = bidRequest.params.video.placement; const rewarded = bidRequest.params.video.rewarded; const inventoryid = bidRequest.params.video.inventoryid; - const VERSION = '3.1.0'; + const VERSION = '3.1.2'; expect(data.imp[0].video.w).to.equal(width); expect(data.imp[0].video.h).to.equal(height); expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); @@ -640,7 +704,10 @@ describe('OneVideoBidAdapter', function () { adid: 123, crid: 2, price: 6.01, - adm: '' + adm: '', + adomain: [ + 'verizonmedia.com' + ], }] }], cur: 'USD' @@ -664,6 +731,9 @@ describe('OneVideoBidAdapter', function () { netRevenue: true, adUnitCode: bidRequest.adUnitCode, renderer: (bidRequest.mediaTypes.video.context === 'outstream') ? newRenderer(bidRequest, bidResponse) : undefined, + meta: { + advertiserDomains: ['verizonmedia.com'] + } }; expect(bidResponse).to.deep.equal(o); }); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index c1462c3814d..e873597ca15 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -105,9 +105,36 @@ describe('onetag', function () { }); }); describe('multi format bidRequest', function () { - const multiFormatBid = createMultiFormatBid(); it('Should return true when correct multi format bid is passed', function () { - expect(spec.isBidRequestValid(multiFormatBid)).to.be.true; + expect(spec.isBidRequestValid(createMultiFormatBid())).to.be.true; + }); + it('Should split multi format bid into two single format bid with same bidId', function() { + const bids = JSON.parse(spec.buildRequests([ createMultiFormatBid() ]).data).bids; + expect(bids.length).to.equal(2); + expect(bids[0].bidId).to.equal(bids[1].bidId); + }); + it('Should retrieve correct request bid when extracting video request data', function() { + const requestBid = createMultiFormatBid(); + const multiFormatRequest = spec.buildRequests([ requestBid ]); + const serverResponse = { + body: { + bids: [ + { + mediaType: BANNER, + requestId: requestBid.bidId, + ad: 'test-banner' + }, { + mediaType: VIDEO, + requestId: requestBid.bidId, + vastUrl: 'test-video' + } + ] + } + }; + const responseBids = spec.interpretResponse(serverResponse, multiFormatRequest); + expect(responseBids.length).to.equal(2); + expect(responseBids[0].ad).to.equal('test-banner'); + expect(responseBids[1].vastUrl).to.equal('test-video'); }); }); }); @@ -142,6 +169,9 @@ describe('onetag', function () { expect(data.wHeight).to.be.a('number'); expect(data.oHeight).to.be.a('number'); expect(data.oWidth).to.be.a('number'); + expect(data.ancestorOrigin).to.satisfy(function (value) { + return value === null || typeof value === 'string'; + }); expect(data.aWidth).to.be.a('number'); expect(data.aHeight).to.be.a('number'); expect(data.sLeft).to.be.a('number'); @@ -153,9 +183,30 @@ describe('onetag', function () { for (let i = 0; i < bids.length; i++) { const bid = bids[i]; if (hasTypeVideo(bid)) { - expect(bid).to.have.all.keys('adUnitCode', 'auctionId', 'bidId', 'bidderRequestId', 'pubId', 'transactionId', 'context', 'mimes', 'playerSize', 'protocols', 'maxDuration', 'api', 'type'); + expect(bid).to.have.all.keys( + 'adUnitCode', + 'auctionId', + 'bidId', + 'bidderRequestId', + 'pubId', + 'transactionId', + 'context', + 'playerSize', + 'mediaTypeInfo', + 'type' + ); } else if (isValid(BANNER, bid)) { - expect(bid).to.have.all.keys('adUnitCode', 'auctionId', 'bidId', 'bidderRequestId', 'pubId', 'transactionId', 'sizes', 'type'); + expect(bid).to.have.all.keys( + 'adUnitCode', + 'auctionId', + 'bidId', + 'bidderRequestId', + 'pubId', + 'transactionId', + 'mediaTypeInfo', + 'sizes', + 'type' + ); } expect(bid.bidId).to.be.a('string'); expect(bid.pubId).to.be.a('string'); @@ -240,6 +291,7 @@ describe('onetag', function () { expect(dataItem.creativeId).to.be.a('string'); expect(dataItem.netRevenue).to.be.a('boolean'); expect(dataItem.currency).to.be.a('string'); + expect(dataItem.meta.advertiserDomains).to.be.an('array'); } }); it('Returns an empty array if response is not valid', function () { @@ -323,6 +375,7 @@ function getBannerVideoResponse() { currency: 'USD', requestId: 'banner', mediaType: BANNER, + adomain: [] }, { cpm: 13, @@ -334,7 +387,8 @@ function getBannerVideoResponse() { requestId: 'videoInstream', vastUrl: 'https://videoinstream.org', videoCacheKey: 'key', - mediaType: VIDEO + mediaType: VIDEO, + adomain: ['test_domain'] }, { cpm: 13, @@ -347,7 +401,8 @@ function getBannerVideoResponse() { requestId: 'videoOutstream', ad: '', rendererUrl: 'https://testRenderer', - mediaType: VIDEO + mediaType: VIDEO, + adomain: [] } ] } diff --git a/test/spec/modules/onomagicBidAdapter_spec.js b/test/spec/modules/onomagicBidAdapter_spec.js index 7c71c3e5764..6ddc0edd477 100644 --- a/test/spec/modules/onomagicBidAdapter_spec.js +++ b/test/spec/modules/onomagicBidAdapter_spec.js @@ -222,7 +222,8 @@ describe('onomagicBidAdapter', function() { 'nurl': '', 'adm': '', 'w': 300, - 'h': 250 + 'h': 250, + 'adomain': ['example.com'] }] }] } @@ -240,7 +241,10 @@ describe('onomagicBidAdapter', function() { 'netRevenue': true, 'mediaType': 'banner', 'ad': `
`, - 'ttl': 60 + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } }]; let result = spec.interpretResponse(response); @@ -258,7 +262,10 @@ describe('onomagicBidAdapter', function() { 'netRevenue': true, 'mediaType': 'banner', 'ad': `
`, - 'ttl': 60 + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } }]; let result = spec.interpretResponse(response); diff --git a/test/spec/modules/open8BidAdapter_spec.js b/test/spec/modules/open8BidAdapter_spec.js deleted file mode 100644 index 506742bef9e..00000000000 --- a/test/spec/modules/open8BidAdapter_spec.js +++ /dev/null @@ -1,251 +0,0 @@ -import { spec } from 'modules/open8BidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -const ENDPOINT = 'https://as.vt.open8.com/v1/control/prebid'; - -describe('Open8Adapter', function() { - const adapter = newBidder(spec); - - describe('isBidRequestValid', function() { - let bid = { - 'bidder': 'open8', - 'params': { - 'slotKey': 'slotkey1234' - }, - 'adUnitCode': 'adunit', - 'sizes': [[300, 250]], - 'bidId': 'bidid1234', - 'bidderRequestId': 'requestid1234', - 'auctionId': 'auctionid1234', - }; - - it('should return true when required params found', function() { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function() { - bid.params = { - ' slotKey': 0 - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function() { - let bidRequests = [ - { - 'bidder': 'open8', - 'params': { - 'slotKey': 'slotkey1234' - }, - 'adUnitCode': 'adunit', - 'sizes': [[300, 250]], - 'bidId': 'bidid1234', - 'bidderRequestId': 'requestid1234', - 'auctionId': 'auctionid1234', - } - ]; - - it('sends bid request to ENDPOINT via GET', function() { - const requests = spec.buildRequests(bidRequests); - expect(requests[0].url).to.equal(ENDPOINT); - expect(requests[0].method).to.equal('GET'); - }); - }); - describe('interpretResponse', function() { - const bannerResponse = { - slotKey: 'slotkey1234', - userId: 'userid1234', - impId: 'impid1234', - media: 'TEST_MEDIA', - nurl: 'https://example/win', - isAdReturn: true, - syncPixels: ['https://example/sync/pixel.gif'], - syncIFs: [], - ad: { - bidId: 'TEST_BID_ID', - price: 1234.56, - creativeId: 'creativeid1234', - dealId: 'TEST_DEAL_ID', - currency: 'JPY', - ds: 876, - spd: 1234, - fa: 5678, - pr: 'pr1234', - mr: 'mr1234', - nurl: 'https://example/win', - adType: 2, - banner: { - w: 300, - h: 250, - adm: '
', - imps: ['https://example.com/imp'] - } - } - }; - const videoResponse = { - slotKey: 'slotkey1234', - userId: 'userid1234', - impId: 'impid1234', - media: 'TEST_MEDIA', - isAdReturn: true, - syncPixels: ['https://example/sync/pixel.gif'], - syncIFs: [], - ad: { - bidId: 'TEST_BID_ID', - price: 1234.56, - creativeId: 'creativeid1234', - dealId: 'TEST_DEAL_ID', - currency: 'JPY', - ds: 876, - spd: 1234, - fa: 5678, - pr: 'pr1234', - mr: 'mr1234', - nurl: 'https://example/win', - adType: 1, - video: { - purl: 'https://playerexample.js', - vastXml: '', - w: 320, - h: 180 - }, - } - }; - - it('should get correct banner bid response', function() { - let expectedResponse = [{ - 'slotKey': 'slotkey1234', - 'userId': 'userid1234', - 'impId': 'impid1234', - 'media': 'TEST_MEDIA', - 'ds': 876, - 'spd': 1234, - 'fa': 5678, - 'pr': 'pr1234', - 'mr': 'mr1234', - 'nurl': 'https://example/win', - 'requestId': 'requestid1234', - 'cpm': 1234.56, - 'creativeId': 'creativeid1234', - 'dealId': 'TEST_DEAL_ID', - 'width': 300, - 'height': 250, - 'ad': "
", - 'mediaType': 'banner', - 'currency': 'JPY', - 'ttl': 360, - 'netRevenue': true - }]; - - let bidderRequest; - let result = spec.interpretResponse({ body: bannerResponse }, { bidderRequest }); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); - }); - - it('handles video responses', function() { - let expectedResponse = [{ - 'slotKey': 'slotkey1234', - 'userId': 'userid1234', - 'impId': 'impid1234', - 'media': 'TEST_MEDIA', - 'ds': 876, - 'spd': 1234, - 'fa': 5678, - 'pr': 'pr1234', - 'mr': 'mr1234', - 'nurl': 'https://example/win', - 'requestId': 'requestid1234', - 'cpm': 1234.56, - 'creativeId': 'creativeid1234', - 'dealId': 'TEST_DEAL_ID', - 'width': 320, - 'height': 180, - 'vastXml': '', - 'mediaType': 'video', - 'renderer': {}, - 'adResponse': {}, - 'currency': 'JPY', - 'ttl': 360, - 'netRevenue': true - }]; - - let bidderRequest; - let result = spec.interpretResponse({ body: videoResponse }, { bidderRequest }); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); - }); - - it('handles nobid responses', function() { - let response = { - isAdReturn: false, - 'ad': {} - }; - - let bidderRequest; - let result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result.length).to.equal(0); - }); - }); - - describe('getUserSyncs', function() { - const imgResponse1 = { - body: { - 'isAdReturn': true, - 'ad': { /* ad body */ }, - 'syncPixels': [ - 'https://example.test/1' - ] - } - }; - - const imgResponse2 = { - body: { - 'isAdReturn': true, - 'ad': { /* ad body */ }, - 'syncPixels': [ - 'https://example.test/2' - ] - } - }; - - const ifResponse = { - body: { - 'isAdReturn': true, - 'ad': { /* ad body */ }, - 'syncIFs': [ - 'https://example.test/3' - ] - } - }; - - it('should use a sync img url from first response', function() { - const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imgResponse1, imgResponse2, ifResponse]); - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'https://example.test/1' - } - ]); - }); - - it('handle ifs response', function() { - const syncs = spec.getUserSyncs({ iframeEnabled: true }, [ifResponse]); - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'https://example.test/3' - } - ]); - }); - - it('handle empty response (e.g. timeout)', function() { - const syncs = spec.getUserSyncs({ pixelEnabled: true }, []); - expect(syncs).to.deep.equal([]); - }); - - it('returns empty syncs when not enabled', function() { - const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imgResponse1]); - expect(syncs).to.deep.equal([]); - }); - }); -}); diff --git a/test/spec/modules/openwebBidAdapter_spec.js b/test/spec/modules/openwebBidAdapter_spec.js new file mode 100644 index 00000000000..c515c21690a --- /dev/null +++ b/test/spec/modules/openwebBidAdapter_spec.js @@ -0,0 +1,387 @@ +import { expect } from 'chai'; +import { spec } from 'modules/openwebBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; + +const DEFAULT_ADATPER_REQ = { bidderCode: 'openweb' }; +const DISPLAY_REQUEST = { + 'bidder': 'openweb', + 'params': { + 'aid': 12345 + }, + 'schain': { ver: 1 }, + 'userId': { criteo: 2 }, + 'mediaTypes': { 'banner': { 'sizes': [300, 250] } }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d', +}; + +const VIDEO_REQUEST = { + 'bidder': 'openweb', + 'mediaTypes': { + 'video': { + 'playerSize': [[480, 360], [640, 480]] + } + }, + 'params': { + 'aid': 12345 + }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d' +}; + +const ADPOD_REQUEST = { + 'bidder': 'openweb', + 'mediaTypes': { + 'video': { + 'context': 'adpod', + 'playerSize': [[640, 480]], + 'anyField': 10 + } + }, + 'params': { + 'aid': 12345 + }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '2e41f65424c87c' +}; + +const SERVER_VIDEO_RESPONSE = { + 'source': { 'aid': 12345, 'pubId': 54321 }, + 'bids': [{ + 'vastUrl': 'vastUrl', + 'requestId': '2e41f65424c87c', + 'url': '44F2AEB9BFC881B3', + 'creative_id': 342516, + 'durationSeconds': 30, + 'cmpId': 342516, + 'height': 480, + 'cur': 'USD', + 'width': 640, + 'cpm': 0.9, + 'adomain': ['a.com'] + }] +}; +const SERVER_OUSTREAM_VIDEO_RESPONSE = SERVER_VIDEO_RESPONSE; +const SERVER_DISPLAY_RESPONSE = { + 'source': { 'aid': 12345, 'pubId': 54321 }, + 'bids': [{ + 'ad': '', + 'adUrl': 'adUrl', + 'requestId': '2e41f65424c87c', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 250, + 'cur': 'USD', + 'width': 300, + 'cpm': 0.9 + }], + 'cookieURLs': ['link1', 'link2'] +}; +const SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS = { + 'source': { 'aid': 12345, 'pubId': 54321 }, + 'bids': [{ + 'ad': '', + 'requestId': '2e41f65424c87c', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 250, + 'cur': 'USD', + 'width': 300, + 'cpm': 0.9 + }], + 'cookieURLs': ['link3', 'link4'], + 'cookieURLSTypes': ['image', 'iframe'] +}; + +const videoBidderRequest = { + bidderCode: 'bidderCode', + bids: [{ mediaTypes: { video: {} }, bidId: '2e41f65424c87c' }] +}; + +const displayBidderRequest = { + bidderCode: 'bidderCode', + bids: [{ bidId: '2e41f65424c87c' }] +}; + +const displayBidderRequestWithConsents = { + bidderCode: 'bidderCode', + bids: [{ bidId: '2e41f65424c87c' }], + gdprConsent: { + gdprApplies: true, + consentString: 'test' + }, + uspConsent: 'iHaveIt' +}; + +const videoEqResponse = [{ + vastUrl: 'vastUrl', + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'video', + netRevenue: true, + currency: 'USD', + height: 480, + width: 640, + ttl: 300, + cpm: 0.9, + meta: { + advertiserDomains: ['a.com'] + } +}]; + +const displayEqResponse = [{ + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'banner', + netRevenue: true, + currency: 'USD', + ad: '', + adUrl: 'adUrl', + height: 250, + width: 300, + ttl: 300, + cpm: 0.9, + meta: { + advertiserDomains: [] + } + +}]; + +describe('openwebBidAdapter', () => { + const adapter = newBidder(spec); + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('user syncs', () => { + describe('as image', () => { + it('should be returned if pixel enabled', () => { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[0]]); + expect(syncs.map(s => s.type)).to.deep.equal(['image']); + }) + }) + + describe('as iframe', () => { + it('should be returned if iframe enabled', () => { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[1]]); + expect(syncs.map(s => s.type)).to.deep.equal(['iframe']); + }) + }) + + describe('user sync', () => { + it('should not be returned if passed syncs where already used', () => { + const syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs).to.deep.equal([]); + }) + + it('should not be returned if pixel not set', () => { + const syncs = spec.getUserSyncs({}, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + + expect(syncs).to.be.empty; + }); + }); + describe('user syncs with both types', () => { + it('should be returned if pixel and iframe enabled', () => { + const mockedServerResponse = Object.assign({}, SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS, { 'cookieURLs': ['link5', 'link6'] }); + const syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{ body: mockedServerResponse }]); + + expect(syncs.map(s => s.url)).to.deep.equal(mockedServerResponse.cookieURLs); + expect(syncs.map(s => s.type)).to.deep.equal(mockedServerResponse.cookieURLSTypes); + }); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(VIDEO_REQUEST)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, VIDEO_REQUEST); + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let videoBidRequests = [VIDEO_REQUEST]; + let displayBidRequests = [DISPLAY_REQUEST]; + let videoAndDisplayBidRequests = [DISPLAY_REQUEST, VIDEO_REQUEST]; + const displayRequest = spec.buildRequests(displayBidRequests, DEFAULT_ADATPER_REQ); + const videoRequest = spec.buildRequests(videoBidRequests, DEFAULT_ADATPER_REQ); + const videoAndDisplayRequests = spec.buildRequests(videoAndDisplayBidRequests, DEFAULT_ADATPER_REQ); + + it('building requests as arrays', () => { + expect(videoRequest).to.be.a('array'); + expect(displayRequest).to.be.a('array'); + expect(videoAndDisplayRequests).to.be.a('array'); + }) + + it('sending as POST', () => { + const postActionMethod = 'POST' + const comparator = br => br.method === postActionMethod; + expect(videoRequest.every(comparator)).to.be.true; + expect(displayRequest.every(comparator)).to.be.true; + expect(videoAndDisplayRequests.every(comparator)).to.be.true; + }); + it('forms correct ADPOD request', () => { + const pbBidReqData = spec.buildRequests([ADPOD_REQUEST], DEFAULT_ADATPER_REQ)[0].data; + const impRequest = pbBidReqData.BidRequests[0] + expect(impRequest.AdType).to.be.equal('video'); + expect(impRequest.Adpod).to.be.a('object'); + expect(impRequest.Adpod.anyField).to.be.equal(10); + }) + it('sends correct video bid parameters', () => { + const data = videoRequest[0].data; + + const eq = { + CallbackId: '84ab500420319d', + AdType: 'video', + Aid: 12345, + Sizes: '480x360,640x480', + PlacementId: 'adunit-code' + }; + expect(data.BidRequests[0]).to.deep.equal(eq); + }); + + it('sends correct display bid parameters', () => { + const data = displayRequest[0].data; + + const eq = { + CallbackId: '84ab500420319d', + AdType: 'display', + Aid: 12345, + Sizes: '300x250', + PlacementId: 'adunit-code' + }; + + expect(data.BidRequests[0]).to.deep.equal(eq); + }); + + it('sends correct video and display bid parameters', () => { + const bidRequests = videoAndDisplayRequests[0].data; + const expectedBidReqs = [{ + CallbackId: '84ab500420319d', + AdType: 'display', + Aid: 12345, + Sizes: '300x250', + PlacementId: 'adunit-code' + }, { + CallbackId: '84ab500420319d', + AdType: 'video', + Aid: 12345, + Sizes: '480x360,640x480', + PlacementId: 'adunit-code' + }] + + expect(bidRequests.BidRequests).to.deep.equal(expectedBidReqs); + }); + + describe('publisher environment', () => { + const sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake((key) => { + const config = { + 'coppa': true + }; + return config[key]; + }); + const bidRequestWithPubSettingsData = spec.buildRequests([DISPLAY_REQUEST], displayBidderRequestWithConsents)[0].data; + sandbox.restore(); + it('sets GDPR', () => { + expect(bidRequestWithPubSettingsData.GDPR).to.be.equal(1); + expect(bidRequestWithPubSettingsData.GDPRConsent).to.be.equal(displayBidderRequestWithConsents.gdprConsent.consentString); + }); + it('sets USP', () => { + expect(bidRequestWithPubSettingsData.USP).to.be.equal(displayBidderRequestWithConsents.uspConsent); + }) + it('sets Coppa', () => { + expect(bidRequestWithPubSettingsData.Coppa).to.be.equal(1); + }) + it('sets Schain', () => { + expect(bidRequestWithPubSettingsData.Schain).to.be.deep.equal(DISPLAY_REQUEST.schain); + }) + it('sets UserId\'s', () => { + expect(bidRequestWithPubSettingsData.UserIds).to.be.deep.equal(DISPLAY_REQUEST.userId); + }) + }) + }); + + describe('interpretResponse', () => { + let serverResponse; + let adapterRequest; + let eqResponse; + + afterEach(() => { + serverResponse = null; + adapterRequest = null; + eqResponse = null; + }); + + it('should get correct video bid response', () => { + serverResponse = SERVER_VIDEO_RESPONSE; + adapterRequest = videoBidderRequest; + eqResponse = videoEqResponse; + + bidServerResponseCheck(); + }); + + it('should get correct display bid response', () => { + serverResponse = SERVER_DISPLAY_RESPONSE; + adapterRequest = displayBidderRequest; + eqResponse = displayEqResponse; + + bidServerResponseCheck(); + }); + + function bidServerResponseCheck() { + const result = spec.interpretResponse({ body: serverResponse }, { adapterRequest }); + + expect(result).to.deep.equal(eqResponse); + } + + function nobidServerResponseCheck() { + const noBidServerResponse = { bids: [] }; + const noBidResult = spec.interpretResponse({ body: noBidServerResponse }, { adapterRequest }); + + expect(noBidResult.length).to.equal(0); + } + + it('handles video nobid responses', () => { + adapterRequest = videoBidderRequest; + + nobidServerResponseCheck(); + }); + + it('handles display nobid responses', () => { + adapterRequest = displayBidderRequest; + + nobidServerResponseCheck(); + }); + + it('forms correct ADPOD response', () => { + const videoBids = spec.interpretResponse({ body: SERVER_VIDEO_RESPONSE }, { adapterRequest: { bids: [ADPOD_REQUEST] } }); + expect(videoBids[0].video.durationSeconds).to.be.equal(30); + expect(videoBids[0].video.context).to.be.equal('adpod'); + }) + }); +}); diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js index b946efe922d..d7d2d31669c 100644 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -62,8 +62,9 @@ describe('openx analytics adapter', function() { orgId: 'test-org-id', publisherAccountId: 123, publisherPlatformId: 'test-platform-id', + configId: 'my_config', + optimizerConfig: 'my my optimizer', sample: 1.0, - enableV2: true, payloadWaitTime: SLOT_LOAD_WAIT_TIME, payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME }; @@ -292,6 +293,18 @@ describe('openx analytics adapter', function() { it('should track the orgId', function () { expect(auction.publisherAccountId).to.equal(DEFAULT_V2_ANALYTICS_CONFIG.publisherAccountId); }); + + it('should track the optimizerConfig', function () { + expect(auction.optimizerConfig).to.equal(DEFAULT_V2_ANALYTICS_CONFIG.optimizerConfig); + }); + + it('should track the configId', function () { + expect(auction.configId).to.equal(DEFAULT_V2_ANALYTICS_CONFIG.configId); + }); + + it('should track the auction Id', function () { + expect(auction.auctionId).to.equal(auctionInit.auctionId); + }); }); describe('when there is a custom test code', function () { diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 7391c8826bc..3bc53e30eb8 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -122,6 +122,84 @@ describe('OpenxAdapter', function () { } }; + // Sample bid requests + + const BANNER_BID_REQUESTS_WITH_MEDIA_TYPES = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + adUnitCode: '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, + }, { + bidder: 'openx', + params: { + unit: '22', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bid-request-2', + auctionId: 'test-auction-2', + ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-1' } } }, + }]; + + const VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES = [{ + bidder: 'openx', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e', + ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, + }]; + + const MULTI_FORMAT_BID_REQUESTS = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [300, 250] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e', + ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, + }]; + describe('inherited functions', function () { it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); @@ -181,28 +259,8 @@ describe('OpenxAdapter', function () { describe('when request is for a multiformat ad', function () { describe('and request config uses mediaTypes video and banner', () => { - const multiformatBid = { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250]] - }, - video: { - playerSize: [300, 250] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); + expect(spec.isBidRequestValid(MULTI_FORMAT_BID_REQUESTS[0])).to.equal(true); }); }); }); @@ -327,37 +385,7 @@ describe('OpenxAdapter', function () { }); describe('buildRequests for banner ads', function () { - const bidRequestsWithMediaTypes = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '22', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - 'bidId': 'test-bid-id-2', - 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' - }]; + const bidRequestsWithMediaTypes = BANNER_BID_REQUESTS_WITH_MEDIA_TYPES; const bidRequestsWithPlatform = [{ 'bidder': 'openx', @@ -450,7 +478,12 @@ describe('OpenxAdapter', function () { it('should send the adunit codes', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.divIds).to.equal(`${encodeURIComponent(bidRequestsWithMediaTypes[0].adUnitCode)},${encodeURIComponent(bidRequestsWithMediaTypes[1].adUnitCode)}`); + expect(request[0].data.divids).to.equal(`${encodeURIComponent(bidRequestsWithMediaTypes[0].adUnitCode)},${encodeURIComponent(bidRequestsWithMediaTypes[1].adUnitCode)}`); + }); + + it('should send the gpids', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.aucs).to.equal(`${encodeURIComponent('/12345/my-gpt-tag-0')},${encodeURIComponent('/12345/my-gpt-tag-1')}`); }); it('should send ad unit ids when any are defined', function () { @@ -1052,15 +1085,28 @@ describe('OpenxAdapter', function () { intentIqId: '1111-intentiqid', lipb: {lipbid: '1111-lipb'}, lotamePanoramaId: '1111-lotameid', - merkleId: '1111-merkleid', + merkleId: {id: '1111-merkleid'}, netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', parrableId: { eid: 'eidVersion.encryptionKeyReference.encryptedValue' }, pubcid: '1111-pubcid', quantcastId: '1111-quantcastid', - sharedId: '1111-sharedid', tapadId: '111-tapadid', tdid: '1111-tdid', - verizonMediaId: '1111-verizonmediaid', + uid2: {id: '1111-uid2'}, + flocId: {id: '12144', version: 'chrome.1.1'}, + novatiq: {snowflake: '1111-novatiqid'}, + admixerId: '1111-admixerid', + deepintentId: '1111-deepintentid', + dmdId: '111-dmdid', + nextrollId: '1111-nextrollid', + mwOpenLinkId: '1111-mwopenlinkid', + dapId: '1111-dapId', + amxId: '1111-amxid', + kpuid: '1111-kpuid', + publinkId: '1111-publinkid', + naveggId: '1111-naveggid', + imuid: '1111-imuid', + adtelligentId: '1111-adtelligentid' }; // generates the same set of tests for each id provider @@ -1098,6 +1144,15 @@ describe('OpenxAdapter', function () { let userIdValue; // handle cases where userId key refers to an object switch (userIdProviderKey) { + case 'merkleId': + userIdValue = EXAMPLE_DATA_BY_ATTR.merkleId.id; + break; + case 'flocId': + userIdValue = EXAMPLE_DATA_BY_ATTR.flocId.id; + break; + case 'uid2': + userIdValue = EXAMPLE_DATA_BY_ATTR.uid2.id; + break; case 'lipb': userIdValue = EXAMPLE_DATA_BY_ATTR.lipb.lipbid; break; @@ -1107,6 +1162,9 @@ describe('OpenxAdapter', function () { case 'id5id': userIdValue = EXAMPLE_DATA_BY_ATTR.id5id.uid; break; + case 'novatiq': + userIdValue = EXAMPLE_DATA_BY_ATTR.novatiq.snowflake; + break; default: userIdValue = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; } @@ -1222,24 +1280,7 @@ describe('OpenxAdapter', function () { }); describe('buildRequests for video', function () { - const bidRequestsWithMediaTypes = [{ - 'bidder': 'openx', - 'mediaTypes': { - video: { - playerSize: [640, 480] - } - }, - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; + const bidRequestsWithMediaTypes = VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES; const mockBidderRequest = {refererInfo: {}}; it('should send bid request to openx url via GET, with mediaTypes having video parameter', function () { @@ -1254,6 +1295,7 @@ describe('OpenxAdapter', function () { expect(dataParams.auid).to.equal('12345678'); expect(dataParams.vht).to.equal(480); expect(dataParams.vwd).to.equal(640); + expect(dataParams.aucs).to.equal(encodeURIComponent('/12345/my-gpt-tag-0')); }); it('shouldn\'t have the test parameter', function () { @@ -1313,45 +1355,97 @@ describe('OpenxAdapter', function () { expect(request[0].data.ju).to.not.equal(myUrl); }); - describe('when using the openRtb param', function () { - it('should covert the param to a JSON string', function () { - let myOpenRTBObject = {}; + describe('when using the openrtb video params', function () { + it('should parse legacy params.video.openrtb', function () { + let myOpenRTBObject = {mimes: ['application/javascript']}; videoBidRequest.params.video = { openrtb: myOpenRTBObject }; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - expect(request[0].data.openrtb).to.equal(JSON.stringify(myOpenRTBObject)); + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); }); - it("should use the bidRequest's playerSize when it is available", function () { - const width = 200; - const height = 100; - const myOpenRTBObject = {v: height, w: width}; + it('should parse legacy params.openrtb', function () { + let myOpenRTBObject = {mimes: ['application/javascript']}; + videoBidRequest.params.openrtb = myOpenRTBObject; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + }); + + it('should parse legacy params.video', function () { + let myOpenRTBObject = {mimes: ['application/javascript']}; + videoBidRequest.params.video = myOpenRTBObject; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + }); + + it('should parse legacy params.video as full openrtb', function () { + let myOpenRTBObject = {imp: [{video: {mimes: ['application/javascript']}}]}; + videoBidRequest.params.video = myOpenRTBObject; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + }); + + it('should parse legacy video.openrtb', function () { + let myOpenRTBObject = {mimes: ['application/javascript']}; videoBidRequest.params.video = { openrtb: myOpenRTBObject }; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + }); + + it('should omit filtered values for legacy', function () { + let myOpenRTBObject = {mimes: ['application/javascript'], dont: 'use'}; + videoBidRequest.params.video = { + openrtb: myOpenRTBObject + }; + const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + + expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + }); + + it('should parse mediatypes.video', function () { + videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] + videoBidRequest.mediaTypes.video.minduration = 15 const request = spec.buildRequests([videoBidRequest], mockBidderRequest); const openRtbRequestParams = JSON.parse(request[0].data.openrtb); + expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); + expect(openRtbRequestParams.imp[0].video.minduration).to.equal(15); + }); - expect(openRtbRequestParams.w).to.not.equal(width); - expect(openRtbRequestParams.v).to.not.equal(height); + it('should filter mediatypes.video', function () { + videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] + videoBidRequest.mediaTypes.video.minnothing = 15 + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + const openRtbRequestParams = JSON.parse(request[0].data.openrtb); + expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); + expect(openRtbRequestParams.imp[0].video.minnothing).to.equal(undefined); }); - it('should use the the openRTB\'s sizing when the bidRequest\'s playerSize is not available', function () { + it("should use the bidRequest's playerSize", function () { const width = 200; const height = 100; const myOpenRTBObject = {v: height, w: width}; videoBidRequest.params.video = { openrtb: myOpenRTBObject }; - videoBidRequest.mediaTypes.video.playerSize = undefined; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - expect(openRtbRequestParams.w).to.equal(width); - expect(openRtbRequestParams.v).to.equal(height); + expect(openRtbRequestParams.imp[0].video.w).to.equal(640); + expect(openRtbRequestParams.imp[0].video.h).to.equal(480); }); }); }); @@ -1439,36 +1533,220 @@ describe('OpenxAdapter', function () { }); describe('buildRequest for multi-format ad', function () { - const multiformatBid = { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250]] - }, - video: { - playerSize: [300, 250] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; + const multiformatBid = MULTI_FORMAT_BID_REQUESTS[0]; let mockBidderRequest = {refererInfo: {}}; it('should default to a banner request', function () { const request = spec.buildRequests([multiformatBid], mockBidderRequest); const dataParams = request[0].data; - expect(dataParams.divIds).to.have.string(multiformatBid.adUnitCode); + expect(dataParams.divids).to.have.string(multiformatBid.adUnitCode); }); }); + describe('buildRequests for all kinds of ads', function () { + utils._each({ + banner: BANNER_BID_REQUESTS_WITH_MEDIA_TYPES[0], + video: VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES[0], + multi: MULTI_FORMAT_BID_REQUESTS[0] + }, (bidRequest, name) => { + describe('with segments', function () { + const TESTS = [ + { + name: 'should send proprietary segment data from ortb2.user.data', + config: { + ortb2: { + user: { + data: [ + {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, + {name: 'dmp2', segment: [{id: 'baz'}]}, + ] + } + } + }, + expect: {sm: 'dmp1/4:foo|bar,dmp2:baz'}, + }, + { + name: 'should send proprietary segment data from ortb2.site.content.data', + config: { + ortb2: { + site: { + content: { + data: [ + {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, + {name: 'dmp2', segment: [{id: 'baz'}]}, + ] + } + } + } + }, + expect: {scsm: 'dmp1/4:foo|bar,dmp2:baz'}, + }, + { + name: 'should send proprietary segment data from both ortb2.site.content.data and ortb2.user.data', + config: { + ortb2: { + user: { + data: [ + {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, + {name: 'dmp2', segment: [{id: 'baz'}]}, + ] + }, + site: { + content: { + data: [ + {name: 'dmp3', ext: {segtax: 5}, segment: [{id: 'foo2'}, {id: 'bar2'}]}, + {name: 'dmp4', segment: [{id: 'baz2'}]}, + ] + } + } + } + }, + expect: { + sm: 'dmp1/4:foo|bar,dmp2:baz', + scsm: 'dmp3/5:foo2|bar2,dmp4:baz2' + }, + }, + { + name: 'should combine same provider segment data from ortb2.user.data', + config: { + ortb2: { + user: { + data: [ + {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, + {name: 'dmp1', ext: {}, segment: [{id: 'baz'}]}, + ] + } + } + }, + expect: {sm: 'dmp1/4:foo|bar,dmp1:baz'}, + }, + { + name: 'should combine same provider segment data from ortb2.site.content.data', + config: { + ortb2: { + site: { + content: { + data: [ + {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, + {name: 'dmp1', ext: {}, segment: [{id: 'baz'}]}, + ] + } + } + } + }, + expect: {scsm: 'dmp1/4:foo|bar,dmp1:baz'}, + }, + { + name: 'should not send any segment data if first party config is incomplete', + config: { + ortb2: { + user: { + data: [ + {name: 'provider-with-no-segments'}, + {segment: [{id: 'segments-with-no-provider'}]}, + {}, + ] + } + } + } + }, + { + name: 'should send first party data segments and liveintent segments from request', + config: { + ortb2: { + user: { + data: [ + {name: 'dmp1', segment: [{id: 'foo'}, {id: 'bar'}]}, + {name: 'dmp2', segment: [{id: 'baz'}]}, + ] + }, + site: { + content: { + data: [ + {name: 'dmp3', ext: {segtax: 5}, segment: [{id: 'foo2'}, {id: 'bar2'}]}, + {name: 'dmp4', segment: [{id: 'baz2'}]}, + ] + } + } + } + }, + request: { + userId: { + lipb: { + lipbid: 'aaa', + segments: ['l1', 'l2'] + }, + }, + }, + expect: { + sm: 'dmp1:foo|bar,dmp2:baz,liveintent:l1|l2', + scsm: 'dmp3/5:foo2|bar2,dmp4:baz2' + }, + }, + { + name: 'should send just liveintent segment from request if no first party config', + config: {}, + request: { + userId: { + lipb: { + lipbid: 'aaa', + segments: ['l1', 'l2'] + }, + }, + }, + expect: {sm: 'liveintent:l1|l2'}, + }, + { + name: 'should send nothing if lipb section does not contain segments', + config: {}, + request: { + userId: { + lipb: { + lipbid: 'aaa', + }, + }, + }, + }, + ]; + utils._each(TESTS, (t) => { + context('in ortb2.user.data', function () { + let bidRequests; + beforeEach(function () { + let fpdConfig = t.config + sinon + .stub(config, 'getConfig') + .withArgs(sinon.match(/^ortb2\.user\.data$|^ortb2\.site\.content\.data$/)) + .callsFake((key) => { + return utils.deepAccess(fpdConfig, key); + }); + bidRequests = [{...bidRequest, ...t.request}]; + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + const mockBidderRequest = {refererInfo: {}}; + it(`${t.name} for type ${name}`, function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest) + expect(request.length).to.equal(1); + if (t.expect) { + for (const key in t.expect) { + expect(request[0].data[key]).to.exist; + expect(request[0].data[key]).to.equal(t.expect[key]); + } + } else { + expect(request[0].data.sm).to.not.exist; + expect(request[0].data.scsm).to.not.exist; + } + }); + }); + }); + }); + }); + }) + describe('interpretResponse for banner ads', function () { beforeEach(function () { sinon.spy(userSync, 'registerSync'); diff --git a/test/spec/modules/operaadsBidAdapter_spec.js b/test/spec/modules/operaadsBidAdapter_spec.js new file mode 100644 index 00000000000..849a3eada3f --- /dev/null +++ b/test/spec/modules/operaadsBidAdapter_spec.js @@ -0,0 +1,737 @@ +import { expect } from 'chai'; +import { spec } from 'modules/operaadsBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes.js'; + +describe('Opera Ads Bid Adapter', function () { + describe('Test isBidRequestValid', function () { + it('undefined bid should return false', function () { + expect(spec.isBidRequestValid()).to.be.false; + }); + + it('null bid should return false', function () { + expect(spec.isBidRequestValid(null)).to.be.false; + }); + + it('bid.params should be set', function () { + expect(spec.isBidRequestValid({})).to.be.false; + }); + + it('bid.params.placementId should be set', function () { + expect(spec.isBidRequestValid({ + params: { endpointId: 'ep12345678', publisherId: 'pub12345678' } + })).to.be.false; + }); + + it('bid.params.publisherId should be set', function () { + expect(spec.isBidRequestValid({ + params: { placementId: 's12345678', endpointId: 'ep12345678' } + })).to.be.false; + }); + + it('bid.params.endpointId should be set', function () { + expect(spec.isBidRequestValid({ + params: { placementId: 's12345678', publisherId: 'pub12345678' } + })).to.be.false; + }); + + it('valid bid should return true', function () { + expect(spec.isBidRequestValid({ + params: { placementId: 's12345678', endpointId: 'ep12345678', publisherId: 'pub12345678' } + })).to.be.true; + }); + }); + + describe('Test buildRequests', function () { + const bidderRequest = { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + auctionStart: Date.now(), + bidderCode: 'myBidderCode', + bidderRequestId: '15246a574e859f', + refererInfo: { + referer: 'http://example.com', + stack: ['http://example.com'] + }, + gdprConsent: { + gdprApplies: true, + consentString: 'IwuyYwpjmnsauyYasIUWwe' + }, + uspConsent: 'Oush3@jmUw82has', + timeout: 3000 + }; + + it('build request object', function () { + const bidRequests = [ + { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { + banner: { sizes: [[300, 250]] } + }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678' + } + }, + { + adUnitCode: 'test-native', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f4622', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { + native: { + title: { + required: true, + len: 20, + }, + image: { + required: true, + sizes: [300, 250], + aspect_ratios: [{ + ratio_width: 1, + ratio_height: 1 + }] + }, + icon: { + required: true, + sizes: [60, 60], + aspect_ratios: [{ + ratio_width: 1, + ratio_height: 1 + }] + }, + sponsoredBy: { + required: true, + len: 20 + }, + body: { + required: true, + len: 140 + }, + cta: { + required: true, + len: 20, + } + } + }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678' + } + }, + { + adUnitCode: 'test-native2', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f4632', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { + native: { + title: {}, + image: {}, + icon: {}, + sponsoredBy: {}, + body: {}, + cta: {} + } + }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678' + } + }, + { + adUnitCode: 'test-native3', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f4633', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { + native: {}, + }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678' + } + }, + { + adUnitCode: 'test-video', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f4623', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [2, 3, 5, 6], + startdelay: 0, + skip: 1, + playbackmethod: [1, 2, 3, 4], + delivery: [1], + api: [1, 2, 5], + } + }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678' + } + }, + { + adUnitCode: 'test-video', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f4643', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { + video: {} + }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678' + } + } + ]; + + let reqs; + + expect(function () { + reqs = spec.buildRequests(bidRequests, bidderRequest); + }).to.not.throw(); + + expect(reqs).to.be.an('array').that.have.lengthOf(bidRequests.length); + + for (let i = 0, len = reqs.length; i < len; i++) { + const req = reqs[i]; + const bidRequest = bidRequests[i]; + + expect(req.method).to.equal('POST'); + expect(req.url).to.equal('https://s.adx.opera.com/ortb/v2/' + + bidRequest.params.publisherId + '?ep=' + bidRequest.params.endpointId); + + expect(req.options).to.be.an('object'); + expect(req.options.contentType).to.contain('application/json'); + expect(req.options.customHeaders).to.be.an('object'); + expect(req.options.customHeaders['x-openrtb-version']).to.equal(2.5); + + expect(req.originalBidRequest).to.equal(bidRequest); + + expect(req.data).to.be.a('string'); + + let requestData; + expect(function () { + requestData = JSON.parse(req.data); + }).to.not.throw(); + + expect(requestData.id).to.equal(bidderRequest.auctionId); + expect(requestData.tmax).to.equal(bidderRequest.timeout); + expect(requestData.test).to.equal(0); + expect(requestData.imp).to.be.an('array').that.have.lengthOf(1); + expect(requestData.device).to.be.an('object'); + expect(requestData.site).to.be.an('object'); + expect(requestData.site.id).to.equal(bidRequest.params.publisherId); + expect(requestData.site.domain).to.not.be.empty; + expect(requestData.site.page).to.equal(bidderRequest.refererInfo.referer); + expect(requestData.at).to.equal(1); + expect(requestData.bcat).to.be.an('array').that.is.empty; + expect(requestData.cur).to.be.an('array').that.not.be.empty; + expect(requestData.user).to.be.an('object'); + + let impItem = requestData.imp[0]; + expect(impItem).to.be.an('object'); + expect(impItem.id).to.equal(bidRequest.bidId); + expect(impItem.tagid).to.equal(bidRequest.params.placementId); + expect(impItem.bidfloor).to.be.a('number'); + + if (bidRequest.mediaTypes.banner) { + expect(impItem.banner).to.be.an('object'); + } else if (bidRequest.mediaTypes.native) { + expect(impItem.native).to.be.an('object'); + } else if (bidRequest.mediaTypes.video) { + expect(impItem.video).to.be.an('object'); + } else { + expect.fail('should not happen'); + } + } + }); + + it('test getBidFloor', function() { + const bidRequests = [ + { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678' + }, + getFloor: function() { + return { + currency: 'USD', + floor: 0.1 + } + } + } + ]; + + const reqs = spec.buildRequests(bidRequests, bidderRequest); + + expect(reqs).to.be.an('array').that.have.lengthOf(1); + + for (const req of reqs) { + let requestData; + expect(function () { + requestData = JSON.parse(req.data); + }).to.not.throw(); + + expect(requestData.imp).to.be.an('array').that.have.lengthOf(1); + expect(requestData.imp[0].bidfloor).to.be.equal(0.1); + expect(requestData.imp[0].bidfloorcur).to.be.equal('USD'); + } + }); + + it('bcat in params should be used', function () { + const bidRequests = [ + { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678', + bcat: ['IAB1-1'] + } + } + ]; + + const reqs = spec.buildRequests(bidRequests, bidderRequest); + + expect(reqs).to.be.an('array').that.have.lengthOf(1); + + for (const req of reqs) { + let requestData; + expect(function () { + requestData = JSON.parse(req.data); + }).to.not.throw(); + + expect(requestData.bcat).to.be.an('array').that.includes('IAB1-1'); + } + }); + + it('sharedid should be used', function () { + const bidRequests = [{ + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { + banner: { sizes: [[300, 250]] } + }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678' + }, + userId: { + sharedid: { + id: '01F5DEQW731Q2VKT031KBKMW5W' + } + }, + userIdAsEids: [{ + source: 'pubcid.org', + uids: [{ + atype: 1, + id: '01F5DEQW731Q2VKT031KBKMW5W' + }] + }] + }]; + + const reqs = spec.buildRequests(bidRequests, bidderRequest); + + let requestData; + expect(function () { + requestData = JSON.parse(reqs[0].data); + }).to.not.throw(); + + expect(requestData.user.buyeruid).to.equal(bidRequests[0].userId.sharedid.id); + }); + + it('pubcid should be used when sharedid is empty', function () { + const bidRequests = [{ + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { + banner: { sizes: [[300, 250]] } + }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678' + }, + userId: { + 'pubcid': '21F5DEQW731Q2VKT031KBKMW5W' + }, + userIdAsEids: [{ + source: 'pubcid.org', + uids: [{ + atype: 1, + id: '21F5DEQW731Q2VKT031KBKMW5W' + }] + }] + }]; + + const reqs = spec.buildRequests(bidRequests, bidderRequest); + + let requestData; + expect(function () { + requestData = JSON.parse(reqs[0].data); + }).to.not.throw(); + + expect(requestData.user.buyeruid).to.equal(bidRequests[0].userId.pubcid); + }); + + it('random uid will be generate when userId is empty', function () { + const bidRequests = [{ + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { + banner: { sizes: [[300, 250]] } + }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678' + } + }]; + + const reqs = spec.buildRequests(bidRequests, bidderRequest); + + let requestData; + expect(function () { + requestData = JSON.parse(reqs[0].data); + }).to.not.throw(); + + expect(requestData.user.buyeruid).to.not.be.empty; + }) + }); + + describe('Test adapter request', function () { + const adapter = newBidder(spec); + + it('adapter.callBids exists and is a function', function () { + expect(adapter.callBids).to.be.a('function'); + }); + }); + + describe('Test response interpretResponse', function () { + it('Test banner interpretResponse', function () { + const serverResponse = { + body: { + 'id': 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + 'seatbid': [ + { + 'bid': [ + { + 'id': '003004d9c05c6bc7fec0', + 'impid': '22c4871113f461', + 'price': 1.04, + 'nurl': 'https://s.adx.opera.com/win', + 'lurl': 'https://s.adx.opera.com/loss', + 'adm': '', + 'adomain': [ + 'opera.com', + ], + 'cid': '0.49379027', + 'crid': '0.49379027', + 'cat': [ + 'IAB9-31', + 'IAB8' + ], + 'language': 'EN', + 'h': 300, + 'w': 250, + 'exp': 500, + 'ext': {} + } + ], + 'seat': 'adv4199760017536' + } + ], + 'bidid': '003004d9c05c6bc7fec0', + 'cur': 'USD' + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + originalBidRequest: { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + params: { + placementId: 's12345678', + publisherId: 'pub123456', + endpointId: 'ep1234566' + }, + src: 'client', + transactionId: '4781e6ac-93c4-42ba-86fe-ab5f278863cf' + } + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bid = serverResponse.body.seatbid[0].bid[0]; + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(BANNER); + expect(bidResponse.requestId).to.equal(bid.impid); + expect(bidResponse.cpm).to.equal(parseFloat(bid.price).toFixed(2)) + expect(bidResponse.currency).to.equal(serverResponse.body.cur); + expect(bidResponse.creativeId).to.equal(bid.crid || bid.id); + expect(bidResponse.netRevenue).to.be.true; + expect(bidResponse.nurl).to.equal(bid.nurl); + expect(bidResponse.lurl).to.equal(bid.lurl); + + expect(bidResponse.meta).to.be.an('object'); + expect(bidResponse.meta.mediaType).to.equal(BANNER); + expect(bidResponse.meta.primaryCatId).to.equal('IAB9-31'); + expect(bidResponse.meta.secondaryCatIds).to.deep.equal(['IAB8']); + expect(bidResponse.meta.advertiserDomains).to.deep.equal(bid.adomain); + expect(bidResponse.meta.clickUrl).to.equal(bid.adomain[0]); + + expect(bidResponse.ad).to.equal(bid.adm); + expect(bidResponse.width).to.equal(bid.w); + expect(bidResponse.height).to.equal(bid.h); + }); + + it('Test video interpretResponse', function () { + const serverResponse = { + body: { + 'id': 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + 'seatbid': [ + { + 'bid': [ + { + 'id': '003004d9c05c6bc7fec0', + 'impid': '22c4871113f461', + 'price': 1.04, + 'nurl': 'https://s.adx.opera.com/win', + 'lurl': 'https://s.adx.opera.com/loss', + 'adm': 'Static VAST TemplateStatic VAST Taghttp://example.com/pixel.gif?asi=[ADSERVINGID]00:00:08http://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://www.jwplayer.com/http://example.com/pixel.gif?r=[REGULATIONS]&gdpr=[GDPRCONSENT]&pu=[PAGEURL]&da=[DEVICEUA] http://example.com/uploads/myPrerollVideo.mp4 https://example.com/adchoices-sm.pnghttps://sample-url.com', + 'adomain': [ + 'opera.com', + ], + 'cid': '0.49379027', + 'crid': '0.49379027', + 'cat': [ + 'IAB9-31', + 'IAB8' + ], + 'language': 'EN', + 'h': 300, + 'w': 250, + 'exp': 500, + 'ext': {} + } + ], + 'seat': 'adv4199760017536' + } + ], + 'bidid': '003004d9c05c6bc7fec0', + 'cur': 'USD' + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + originalBidRequest: { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { video: { context: 'outstream' } }, + params: { + placementId: 's12345678', + publisherId: 'pub123456', + endpointId: 'ep1234566' + }, + src: 'client', + transactionId: '4781e6ac-93c4-42ba-86fe-ab5f278863cf' + } + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bid = serverResponse.body.seatbid[0].bid[0]; + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(VIDEO); + expect(bidResponse.vastXml).to.equal(bid.adm); + expect(bidResponse.width).to.equal(bid.w); + expect(bidResponse.height).to.equal(bid.h); + + expect(bidResponse.adResponse).to.be.an('object'); + expect(bidResponse.renderer).to.be.an('object'); + }); + + it('Test native interpretResponse', function () { + const serverResponse = { + body: { + 'id': 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + 'seatbid': [ + { + 'bid': [ + { + 'id': '003004d9c05c6bc7fec0', + 'impid': '22c4871113f461', + 'price': 1.04, + 'nurl': 'https://s.adx.opera.com/win', + 'lurl': 'https://s.adx.opera.com/loss', + 'adm': '{"native":{"ver":"1.1","assets":[{"id":1,"required":1,"title":{"text":"The first personal browser"}},{"id":2,"required":1,"img":{"url":"https://res.adx.opera.com/xxx.png","w":720,"h":1280}},{"id":3,"required":1,"img":{"url":"https://res.adx.opera.com/xxx.png","w":60,"h":60}},{"id":4,"required":1,"data":{"value":"Download Opera","len":14}},{"id":5,"required":1,"data":{"value":"Opera","len":5}},{"id":6,"required":1,"data":{"value":"Download","len":8}}],"link":{"url":"https://www.opera.com/mobile/opera","clicktrackers":["https://thirdpart-click.tracker.com","https://t-odx.op-mobile.opera.com/click"]},"imptrackers":["https://thirdpart-imp.tracker.com","https://t-odx.op-mobile.opera.com/impr"],"jstracker":""}}', + 'adomain': [ + 'opera.com', + ], + 'cid': '0.49379027', + 'crid': '0.49379027', + 'cat': [ + 'IAB9-31', + 'IAB8' + ], + 'language': 'EN', + 'h': 300, + 'w': 250, + 'exp': 500, + 'ext': {} + } + ], + 'seat': 'adv4199760017536' + } + ], + 'bidid': '003004d9c05c6bc7fec0', + 'cur': 'USD' + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + originalBidRequest: { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { native: { } }, + params: { + placementId: 's12345678', + publisherId: 'pub123456', + endpointId: 'ep1234566' + }, + src: 'client', + transactionId: '4781e6ac-93c4-42ba-86fe-ab5f278863cf' + } + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(NATIVE) + expect(bidResponse.native).to.be.an('object'); + expect(bidResponse.native.clickUrl).is.not.empty; + expect(bidResponse.native.clickTrackers).to.have.lengthOf(2); + expect(bidResponse.native.impressionTrackers).to.have.lengthOf(2); + expect(bidResponse.native.javascriptTrackers).to.have.lengthOf(1); + }); + + it('Test empty server response', function () { + const bidResponses = spec.interpretResponse({}, {}); + + expect(bidResponses).to.be.an('array').that.is.empty; + }); + + it('Test empty bid response', function () { + const bidResponses = spec.interpretResponse({ body: { seatbid: null } }, {}); + + expect(bidResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('Test getUserSyncs with both iframe and pixel disabled', function () { + it('getUserSyncs should return an empty array', function () { + const syncOptions = {}; + expect(spec.getUserSyncs(syncOptions)).to.be.an('array').that.is.empty; + }); + }); + + describe('Test getUserSyncs with iframe enabled', function () { + it('getUserSyncs should return array', function () { + const syncOptions = { + iframeEnabled: true + } + const userSyncPixels = spec.getUserSyncs(syncOptions) + expect(userSyncPixels).to.have.lengthOf(1); + expect(userSyncPixels[0].url).to.equal('https://s.adx.opera.com/usersync/page') + }); + }); + + describe('Test getUserSyncs with pixel enabled', function () { + it('getUserSyncs should return array', function () { + const serverResponse = { + body: { + 'pixels': [ + 'https://b1.com/usersync', + 'https://b2.com/usersync' + ] + } + }; + const syncOptions = { + pixelEnabled: true + } + const userSyncPixels = spec.getUserSyncs(syncOptions, [serverResponse]) + expect(userSyncPixels).to.have.lengthOf(2); + expect(userSyncPixels[0].url).to.equal('https://b1.com/usersync') + expect(userSyncPixels[1].url).to.equal('https://b2.com/usersync') + }); + }); + + describe('Test onTimeout', function () { + it('onTimeout should not throw', function () { + expect(spec.onTimeout()).to.not.throw; + }); + }); + + describe('Test onBidWon', function () { + it('onBidWon should not throw', function () { + expect(spec.onBidWon({nurl: '#', originalCpm: '1.04', currency: 'USD'})).to.not.throw; + }); + }); + + describe('Test onSetTargeting', function () { + it('onSetTargeting should not throw', function () { + expect(spec.onSetTargeting()).to.not.throw; + }); + }); +}); diff --git a/test/spec/modules/optimeraBidAdapter_spec.js b/test/spec/modules/optimeraBidAdapter_spec.js deleted file mode 100644 index ada07fe25c2..00000000000 --- a/test/spec/modules/optimeraBidAdapter_spec.js +++ /dev/null @@ -1,99 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/optimeraBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -describe('OptimeraAdapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }) - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'optimera', - 'params': { - 'clientID': '9999' - }, - 'adUnitCode': 'div-0', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - }) - - describe('buildRequests', function () { - let bid = [ - { - 'adUnitCode': 'div-0', - 'auctionId': '1ab30503e03994', - 'bidId': '313e0afede8cdb', - 'bidder': 'optimera', - 'bidderRequestId': '202be1ce3f6194', - 'params': { - 'clientID': '0' - } - } - ]; - it('buildRequests fires', function () { - let request = spec.buildRequests(bid); - expect(request).to.exist; - expect(request.method).to.equal('GET'); - expect(request.payload).to.exist; - }); - }) - - describe('interpretResponse', function () { - let serverResponse = {}; - serverResponse.body = JSON.parse('{"div-0":["RB_K","728x90K"], "timestamp":["RB_K","1507565666"], "device": { "de": { "div-0":["A1","728x90K"] }, "mo": { "div-0":["RB_K","728x90K"] }, "tb": { "div-0":["RB_K","728x90K"] } } }'); - var bidRequest = { - 'method': 'get', - 'payload': [ - { - 'bidder': 'optimera', - 'params': { - 'clientID': '0' - }, - 'adUnitCode': 'div-0', - 'bidId': '307440db8538ab' - } - ] - } - it('interpresResponse fires', function () { - let bidResponses = spec.interpretResponse(serverResponse, bidRequest); - expect(bidResponses[0].dealId[0]).to.equal('RB_K'); - expect(bidResponses[0].dealId[1]).to.equal('728x90K'); - }); - }); - - describe('interpretResponse with optional device param', function () { - let serverResponse = {}; - serverResponse.body = JSON.parse('{"div-0":["RB_K","728x90K"], "timestamp":["RB_K","1507565666"], "device": { "de": { "div-0":["A1","728x90K"] }, "mo": { "div-0":["RB_K","728x90K"] }, "tb": { "div-0":["RB_K","728x90K"] } } }'); - var bidRequest = { - 'method': 'get', - 'payload': [ - { - 'bidder': 'optimera', - 'params': { - 'clientID': '0', - 'device': 'de' - }, - 'adUnitCode': 'div-0', - 'bidId': '307440db8538ab' - } - ] - } - it('interpresResponse fires', function () { - let bidResponses = spec.interpretResponse(serverResponse, bidRequest); - expect(bidResponses[0].dealId[0]).to.equal('A1'); - expect(bidResponses[0].dealId[1]).to.equal('728x90K'); - }); - }); -}); diff --git a/test/spec/modules/optimeraRtdProvider_spec.js b/test/spec/modules/optimeraRtdProvider_spec.js new file mode 100644 index 00000000000..7883412ab70 --- /dev/null +++ b/test/spec/modules/optimeraRtdProvider_spec.js @@ -0,0 +1,102 @@ +import * as optimeraRTD from '../../../modules/optimeraRtdProvider.js'; +let utils = require('src/utils.js'); + +describe('Optimera RTD sub module', () => { + it('should init, return true, and set the params', () => { + const conf = { + dataProviders: [{ + name: 'optimeraRTD', + params: { + clientID: '9999', + optimeraKeyName: 'optimera', + device: 'de' + } + }] + }; + expect(optimeraRTD.init(conf.dataProviders[0])).to.equal(true); + expect(optimeraRTD.clientID).to.equal('9999'); + expect(optimeraRTD.optimeraKeyName).to.equal('optimera'); + expect(optimeraRTD.device).to.equal('de'); + }); +}); + +describe('Optimera RTD score file url is properly set', () => { + it('Proerly set the score file url', () => { + optimeraRTD.setScores(); + expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost:9876/context.html.js'); + }); +}); + +describe('Optimera RTD score file properly sets targeting values', () => { + const scores = { + 'div-0': ['A1', 'A2'], + 'div-1': ['A3', 'A4'], + 'device': { + 'de': { + 'div-0': ['A5', 'A6'], + 'div-1': ['A7', 'A8'], + 'insights': { + 'ilv': ['div-0'], + 'miv': ['div-4'], + } + }, + 'mo': { + 'div-0': ['A9', 'B0'], + 'div-1': ['B1', 'B2'], + 'insights': { + 'ilv': ['div-1'], + 'miv': ['div-2'], + } + } + }, + 'insights': { + 'ilv': ['div-5'], + 'miv': ['div-6'], + } + }; + it('Properly set the score file url and scores', () => { + optimeraRTD.setScores(JSON.stringify(scores)); + expect(optimeraRTD.optimeraTargeting['div-0']).to.include.ordered.members(['A5', 'A6']); + expect(optimeraRTD.optimeraTargeting['div-1']).to.include.ordered.members(['A7', 'A8']); + expect(window.optimeraInsights.data['ilv']).to.include.ordered.members(['div-0']); + }); +}); + +describe('Optimera RTD targeting object is properly formed', () => { + const adDivs = ['div-0', 'div-1']; + it('applyTargeting properly created the targeting object', () => { + const targeting = optimeraRTD.returnTargetingData(adDivs); + expect(targeting).to.deep.include({'div-0': {'optimera': [['A5', 'A6']]}}); + expect(targeting).to.deep.include({'div-1': {'optimera': [['A7', 'A8']]}}); + }); +}); + +describe('Optimera RTD error logging', () => { + let utilsLogErrorStub; + + beforeEach(function () { + utilsLogErrorStub = sinon.stub(utils, 'logError'); + }); + afterEach(function () { + utilsLogErrorStub.restore(); + }); + + it('ommitting clientID should log an error', () => { + const conf = { + dataProviders: [{ + name: 'optimeraRTD', + params: { + optimeraKeyName: 'optimera', + device: 'de' + } + }] + }; + optimeraRTD.init(conf.dataProviders[0]); + expect(utils.logError.called).to.equal(true); + }); + + it('if adUnits is not an array should log an error', () => { + optimeraRTD.returnTargetingData('test'); + expect(utils.logError.called).to.equal(true); + }); +}); diff --git a/test/spec/modules/optoutBidAdapter_spec.js b/test/spec/modules/optoutBidAdapter_spec.js new file mode 100644 index 00000000000..4d7c25d12bc --- /dev/null +++ b/test/spec/modules/optoutBidAdapter_spec.js @@ -0,0 +1,115 @@ +import { expect } from 'chai'; +import { spec } from 'modules/optoutBidAdapter.js'; +import {config} from 'src/config.js'; + +describe('optoutAdapterTest', function () { + describe('bidRequestValidity', function () { + it('bidRequest with adslot param', function () { + expect(spec.isBidRequestValid({ + bidder: 'optout', + params: { + 'adslot': 'prebid_demo', + 'publisher': '8' + } + })).to.equal(true); + }); + + it('bidRequest with no adslot param', function () { + expect(spec.isBidRequestValid({ + bidder: 'optout', + params: { + 'publisher': '8' + } + })).to.equal(false); + }); + + it('bidRequest with no publisher param', function () { + expect(spec.isBidRequestValid({ + bidder: 'optout', + params: { + 'adslot': 'prebid_demo' + } + })).to.equal(false); + }); + + it('bidRequest without params', function () { + expect(spec.isBidRequestValid({ + bidder: 'optout', + params: { } + })).to.equal(false); + }); + }); + + describe('bidRequest', function () { + const bidRequests = [{ + 'bidder': 'optout', + 'params': { + 'adslot': 'prebid_demo', + 'publisher': '8' + }, + 'adUnitCode': 'aaa', + 'transactionId': '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', + 'bidId': '9304jr394ddfj', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': '5c66da22-426a-4bac-b153-77360bef5337' + }, + { + 'bidder': 'optout', + 'params': { + 'adslot': 'testslot2', + 'publisher': '2' + }, + 'adUnitCode': 'bbb', + 'transactionId': '193995b4-7122-4739-959b-2463282a138b', + 'bidId': '893j4f94e8jei', + 'bidderRequestId': '70deaff71c281d', + 'gdprConsent': { + consentString: '', + gdprApplies: true, + apiVersion: 2 + }, + 'auctionId': 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' + }]; + + it('bidRequest HTTP method', function () { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + + it('bidRequest url without consent', function () { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.url).to.match(new RegExp('adscience-nocookie\\.nl/prebid/display')); + }); + }); + + it('bidRequest id', function () { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].data.requestId).to.equal('9304jr394ddfj'); + expect(requests[1].data.requestId).to.equal('893j4f94e8jei'); + }); + + it('bidRequest with config for currency', function () { + config.setConfig({ + currency: { + adServerCurrency: 'USD', + granularityMultiplier: 1 + } + }) + + const requests = spec.buildRequests(bidRequests); + expect(requests[0].data.cur.adServerCurrency).to.equal('USD'); + expect(requests[1].data.cur.adServerCurrency).to.equal('USD'); + }); + + it('bidRequest without config for currency', function () { + config.resetConfig(); + + const requests = spec.buildRequests(bidRequests); + expect(requests[0].data.cur.adServerCurrency).to.equal('EUR'); + expect(requests[1].data.cur.adServerCurrency).to.equal('EUR'); + }); + }); +}); diff --git a/test/spec/modules/orbidderBidAdapter_spec.js b/test/spec/modules/orbidderBidAdapter_spec.js index df551311c0b..750524cf47f 100644 --- a/test/spec/modules/orbidderBidAdapter_spec.js +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -1,10 +1,12 @@ import {expect} from 'chai'; import {spec} from 'modules/orbidderBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import * as _ from 'lodash'; +import { BANNER, NATIVE } from '../../../src/mediaTypes.js'; describe('orbidderBidAdapter', () => { const adapter = newBidder(spec); - const defaultBidRequest = { + const defaultBidRequestBanner = { bidId: 'd66fa86787e0b0ca900a96eacfd5f0bb', auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d8', transactionId: 'd58851660c0c4461e4aa06344fc9c0c6', @@ -14,6 +16,38 @@ describe('orbidderBidAdapter', () => { params: { 'accountId': 'string1', 'placementId': 'string2' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + } + }; + + const defaultBidRequestNative = { + bidId: 'd66fa86787e0b0ca900a96eacfd5f0bc', + auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d9', + transactionId: 'd58851660c0c4461e4aa06344fc9c0c7', + bidRequestCount: 1, + adUnitCode: 'adunit-code-native', + sizes: [], + params: { + 'accountId': 'string3', + 'placementId': 'string4' + }, + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true, + sizes: [300, 250] + }, + sponsoredBy: { + required: true + } + } } }; @@ -41,36 +75,64 @@ describe('orbidderBidAdapter', () => { }); describe('isBidRequestValid', () => { - it('should return true when required params found', () => { - expect(spec.isBidRequestValid(defaultBidRequest)).to.equal(true); + it('banner: should return true when required params found', () => { + expect(spec.isBidRequestValid(defaultBidRequestBanner)).to.equal(true); + }); + + it('native: should return true when required params found', () => { + expect(spec.isBidRequestValid(defaultBidRequestNative)).to.equal(true); + }); + + it('banner: accepts optional profile object', () => { + const bidRequest = deepClone(defaultBidRequestBanner); + bidRequest.params.profile = {'key': 'value'}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }); - it('accepts optional profile object', () => { - const bidRequest = deepClone(defaultBidRequest); + it('native: accepts optional profile object', () => { + const bidRequest = deepClone(defaultBidRequestNative); bidRequest.params.profile = {'key': 'value'}; expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }); - it('performs type checking', () => { - const bidRequest = deepClone(defaultBidRequest); + it('banner: performs type checking', () => { + const bidRequest = deepClone(defaultBidRequestBanner); bidRequest.params.accountId = 1; // supposed to be a string expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); - it('doesn\'t accept malformed profile', () => { - const bidRequest = deepClone(defaultBidRequest); + it('native: performs type checking', () => { + const bidRequest = deepClone(defaultBidRequestNative); + bidRequest.params.accountId = 1; // supposed to be a string + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('banner: doesn\'t accept malformed profile', () => { + const bidRequest = deepClone(defaultBidRequestBanner); + bidRequest.params.profile = 'another not usable string'; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('native: doesn\'t accept malformed profile', () => { + const bidRequest = deepClone(defaultBidRequestNative); bidRequest.params.profile = 'another not usable string'; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); - it('should return false when required params are not passed', () => { - const bidRequest = deepClone(defaultBidRequest); + it('banner: should return false when required params are not passed', () => { + const bidRequest = deepClone(defaultBidRequestBanner); delete bidRequest.params; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); - it('accepts optional bidfloor', () => { - const bidRequest = deepClone(defaultBidRequest); + it('native: should return false when required params are not passed', () => { + const bidRequest = deepClone(defaultBidRequestNative); + delete bidRequest.params; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('banner: accepts optional bidfloor', () => { + const bidRequest = deepClone(defaultBidRequestBanner); bidRequest.params.bidfloor = 123; expect(spec.isBidRequestValid(bidRequest)).to.equal(true); @@ -78,15 +140,19 @@ describe('orbidderBidAdapter', () => { expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }); - it('doesn\'t accept malformed bidfloor', () => { - const bidRequest = deepClone(defaultBidRequest); - bidRequest.params.bidfloor = 'another not usable string'; - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + it('native: accepts optional bidfloor', () => { + const bidRequest = deepClone(defaultBidRequestNative); + bidRequest.params.bidfloor = 123; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + + bidRequest.params.bidfloor = 1.23; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }); }); describe('buildRequests', () => { - const request = buildRequest(defaultBidRequest); + const request = buildRequest(defaultBidRequestBanner); + const nativeRequest = buildRequest(defaultBidRequestNative); it('sends bid request to endpoint via https using post', () => { expect(request.method).to.equal('POST'); @@ -98,33 +164,85 @@ describe('orbidderBidAdapter', () => { expect(request.data.v).to.equal($$PREBID_GLOBAL$$.version); }); - it('sends correct bid parameters', () => { - // we add two, because we add referer information and version from bidderRequest object - expect(Object.keys(request.data).length).to.equal(Object.keys(defaultBidRequest).length + 2); + it('banner: sends correct bid parameters', () => { + // we add two, because we add pageUrl and version from bidderRequest object + expect(Object.keys(request.data).length).to.equal(Object.keys(defaultBidRequestBanner).length + 2); + + expect(request.data.bidId).to.equal(defaultBidRequestBanner.bidId); + expect(request.data.auctionId).to.equal(defaultBidRequestBanner.auctionId); + expect(request.data.transactionId).to.equal(defaultBidRequestBanner.transactionId); + expect(request.data.bidRequestCount).to.equal(defaultBidRequestBanner.bidRequestCount); + expect(request.data.adUnitCode).to.equal(defaultBidRequestBanner.adUnitCode); expect(request.data.pageUrl).to.equal('https://localhost:9876/'); - // expect(request.data.referrer).to.equal(''); - Object.keys(defaultBidRequest).forEach((key) => { - expect(defaultBidRequest[key]).to.equal(request.data[key]); + expect(request.data.v).to.equal($$PREBID_GLOBAL$$.version); + expect(request.data.sizes).to.equal(defaultBidRequestBanner.sizes); + + expect(_.isEqual(request.data.params, defaultBidRequestBanner.params)).to.be.true; + expect(_.isEqual(request.data.mediaTypes, defaultBidRequestBanner.mediaTypes)).to.be.true; + }); + + it('native: sends correct bid parameters', () => { + // we add two, because we add pageUrl and version from bidderRequest object + expect(Object.keys(nativeRequest.data).length).to.equal(Object.keys(defaultBidRequestNative).length + 2); + + expect(nativeRequest.data.bidId).to.equal(defaultBidRequestNative.bidId); + expect(nativeRequest.data.auctionId).to.equal(defaultBidRequestNative.auctionId); + expect(nativeRequest.data.transactionId).to.equal(defaultBidRequestNative.transactionId); + expect(nativeRequest.data.bidRequestCount).to.equal(defaultBidRequestNative.bidRequestCount); + expect(nativeRequest.data.adUnitCode).to.equal(defaultBidRequestNative.adUnitCode); + expect(nativeRequest.data.pageUrl).to.equal('https://localhost:9876/'); + expect(nativeRequest.data.v).to.equal($$PREBID_GLOBAL$$.version); + expect(nativeRequest.data.sizes).to.be.empty; + + expect(_.isEqual(nativeRequest.data.params, defaultBidRequestNative.params)).to.be.true; + expect(_.isEqual(nativeRequest.data.mediaTypes, defaultBidRequestNative.mediaTypes)).to.be.true; + }); + + it('banner: handles empty gdpr object', () => { + const request = buildRequest(defaultBidRequestBanner, { + gdprConsent: {} }); + expect(request.data.gdprConsent.consentRequired).to.be.equal(false); }); - it('handles empty gdpr object', () => { - const request = buildRequest(defaultBidRequest, { + it('native: handles empty gdpr object', () => { + const request = buildRequest(defaultBidRequestNative, { gdprConsent: {} }); expect(request.data.gdprConsent.consentRequired).to.be.equal(false); }); - it('handles non-existent gdpr object', () => { - const request = buildRequest(defaultBidRequest, { + it('banner: handles non-existent gdpr object', () => { + const request = buildRequest(defaultBidRequestBanner, { gdprConsent: null }); expect(request.data.gdprConsent).to.be.undefined; }); - it('handles properly filled gdpr object where gdpr applies', () => { + it('native: handles non-existent gdpr object', () => { + const request = buildRequest(defaultBidRequestNative, { + gdprConsent: null + }); + expect(request.data.gdprConsent).to.be.undefined; + }); + + it('banner: handles properly filled gdpr object where gdpr applies', () => { + const consentString = 'someWeirdString'; + const request = buildRequest(defaultBidRequestBanner, { + gdprConsent: { + gdprApplies: true, + consentString: consentString + } + }); + + const gdprConsent = request.data.gdprConsent; + expect(gdprConsent.consentRequired).to.be.equal(true); + expect(gdprConsent.consentString).to.be.equal(consentString); + }); + + it('native: handles properly filled gdpr object where gdpr applies', () => { const consentString = 'someWeirdString'; - const request = buildRequest(defaultBidRequest, { + const request = buildRequest(defaultBidRequestNative, { gdprConsent: { gdprApplies: true, consentString: consentString @@ -136,9 +254,9 @@ describe('orbidderBidAdapter', () => { expect(gdprConsent.consentString).to.be.equal(consentString); }); - it('handles properly filled gdpr object where gdpr does not apply', () => { + it('banner: handles properly filled gdpr object where gdpr does not apply', () => { const consentString = 'someWeirdString'; - const request = buildRequest(defaultBidRequest, { + const request = buildRequest(defaultBidRequestBanner, { gdprConsent: { gdprApplies: false, consentString: consentString @@ -149,10 +267,45 @@ describe('orbidderBidAdapter', () => { expect(gdprConsent.consentRequired).to.be.equal(false); expect(gdprConsent.consentString).to.be.equal(consentString); }); + + it('native: handles properly filled gdpr object where gdpr does not apply', () => { + const consentString = 'someWeirdString'; + const request = buildRequest(defaultBidRequestNative, { + gdprConsent: { + gdprApplies: false, + consentString: consentString + } + }); + + const gdprConsent = request.data.gdprConsent; + expect(gdprConsent.consentRequired).to.be.equal(false); + expect(gdprConsent.consentString).to.be.equal(consentString); + }); + }); + + describe('buildRequests with price floor module', () => { + const bidRequest = deepClone(defaultBidRequestBanner); + bidRequest.params.bidfloor = 1; + bidRequest.getFloor = (floorObj) => { + return { + floor: bidRequest.floors.values['banner|640x480'], + currency: floorObj.currency, + mediaType: floorObj.mediaType + } + }; + + bidRequest.floors = { + currency: 'EUR', + values: { + 'banner|640x480': 15.07 + } + }; + const request = buildRequest(bidRequest); + expect(request.data.params.bidfloor).to.equal(15.07); }); describe('interpretResponse', () => { - it('should get correct bid response', () => { + it('banner: should get correct bid response', () => { const serverResponse = [ { 'width': 300, @@ -163,7 +316,8 @@ describe('orbidderBidAdapter', () => { 'requestId': '30b31c1838de1e', 'ttl': 60, 'netRevenue': true, - 'currency': 'EUR' + 'currency': 'EUR', + 'mediaType': BANNER, } ]; @@ -177,7 +331,48 @@ describe('orbidderBidAdapter', () => { 'ttl': 60, 'currency': 'EUR', 'ad': '', - 'netRevenue': true + 'netRevenue': true, + 'mediaType': BANNER, + } + ]; + + const result = spec.interpretResponse({body: serverResponse}); + expect(result.length).to.equal(expectedResponse.length); + expect(_.isEqual(expectedResponse, serverResponse)).to.be.true; + }); + + it('should get correct bid response with advertiserDomains', () => { + const serverResponse = [ + { + 'width': 300, + 'height': 250, + 'creativeId': '29681110', + 'ad': '', + 'cpm': 0.5, + 'requestId': '30b31c1838de1e', + 'ttl': 60, + 'netRevenue': true, + 'currency': 'EUR', + 'advertiserDomains': ['cm.tavira.pt'], + 'mediaType': BANNER + } + ]; + + const expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'cpm': 0.5, + 'creativeId': '29681110', + 'width': 300, + 'height': 250, + 'ttl': 60, + 'currency': 'EUR', + 'ad': '', + 'netRevenue': true, + 'meta': { + 'advertiserDomains': ['cm.tavira.pt'] + }, + 'mediaType': BANNER } ]; @@ -185,28 +380,140 @@ describe('orbidderBidAdapter', () => { expect(result.length).to.equal(expectedResponse.length); Object.keys(expectedResponse[0]).forEach((key) => { - expect(result[0][key]).to.equal(expectedResponse[0][key]); + expect(result[0][key]).to.deep.equal(expectedResponse[0][key]); }); }); - it('handles broken server response', () => { + it('native: should get correct bid response', () => { + const serverResponse = [ + { + 'creativeId': '29681110', + 'cpm': 0.5, + 'requestId': '30b31c1838de1e', + 'ttl': 60, + 'netRevenue': true, + 'currency': 'EUR', + 'mediaType': NATIVE, + 'native': { + 'image': { + 'url': 'image url', + 'width': 300, + 'height': 250, + }, + 'icon': { + 'url': 'icon url', + 'width': 50, + 'height': 50, + }, + 'impressionTrackers': 'imp tracker', + 'clickUrl': 'click', + 'sponsoredBy': 'brand', + 'cta': 'action', + 'body': 'text', + } + } + ]; + + const expectedResponse = [ + { + 'creativeId': '29681110', + 'cpm': 0.5, + 'requestId': '30b31c1838de1e', + 'ttl': 60, + 'netRevenue': true, + 'currency': 'EUR', + 'mediaType': NATIVE, + 'native': { + 'image': { + 'url': 'image url', + 'width': 300, + 'height': 250, + }, + 'icon': { + 'url': 'icon url', + 'width': 50, + 'height': 50, + }, + 'impressionTrackers': 'imp tracker', + 'clickUrl': 'click', + 'sponsoredBy': 'brand', + 'cta': 'action', + 'body': 'text', + } + } + ]; + + const result = spec.interpretResponse({body: serverResponse}); + + expect(result.length).to.equal(expectedResponse.length); + expect(_.isEqual(expectedResponse, serverResponse)).to.be.true; + }); + + it('banner: handles broken bid response, missing creativeId', () => { const serverResponse = [ { 'ad': '', 'cpm': 0.5, 'requestId': '30b31c1838de1e', - 'ttl': 60 + 'ttl': 60, + 'currency': 'EUR', + 'mediaType': BANNER, + 'width': 300, + 'height': 250, + 'netRevenue': true, } ]; const result = spec.interpretResponse({body: serverResponse}); + expect(result.length).to.equal(0); + }); + it('banner: handles broken bid response, missing ad', () => { + const serverResponse = [ + { + 'cpm': 0.5, + 'requestId': '30b31c1838de1e', + 'ttl': 60, + 'currency': 'EUR', + 'mediaType': BANNER, + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'creativeId': '29681110', + } + ]; + const result = spec.interpretResponse({body: serverResponse}); + expect(result.length).to.equal(0); + }); + + it('native: handles broken bid response, missing impressionTrackers', () => { + const serverResponse = [ + { + 'creativeId': '29681110', + 'cpm': 0.5, + 'requestId': '30b31c1838de1e', + 'ttl': 60, + 'netRevenue': true, + 'currency': 'EUR', + 'mediaType': NATIVE, + 'native': { + 'title': 'native title', + 'sponsoredBy': 'test brand', + 'image': { + 'url': 'image url', + 'width': 300, + 'height': 250, + }, + 'clickUrl': 'click' + } + } + ]; + const result = spec.interpretResponse({body: serverResponse}); expect(result.length).to.equal(0); }); it('handles nobid responses', () => { const serverResponse = []; const result = spec.interpretResponse({body: serverResponse}); - expect(result.length).to.equal(0); }); }); diff --git a/test/spec/modules/otmBidAdapter_spec.js b/test/spec/modules/otmBidAdapter_spec.js index 8ac01c1657e..6eb5768c3af 100644 --- a/test/spec/modules/otmBidAdapter_spec.js +++ b/test/spec/modules/otmBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; -import {spec} from 'modules/otmBidAdapter.js'; +import {spec} from 'modules/otmBidAdapter'; -describe('otmBidAdapterTests', function () { +describe('otmBidAdapter', function () { it('validate_pub_params', function () { expect(spec.isBidRequestValid({ bidder: 'otm', @@ -29,44 +29,6 @@ describe('otmBidAdapterTests', function () { expect(req_data.bidid).to.equal('bid1234'); }); - it('validate_best_size_select', function () { - // when: - let bidRequestData = [{ - bidId: 'bid1234', - bidder: 'otm', - params: { - tid: '123', - bidfloor: 20 - }, - sizes: [[300, 500], [300, 600], [240, 400], [300, 50]] - }]; - - let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; - - // then: - expect(req_data.w).to.equal(240); - expect(req_data.h).to.equal(400); - - // when: - bidRequestData = [{ - bidId: 'bid1234', - bidder: 'otm', - params: { - tid: '123', - bidfloor: 20 - }, - sizes: [[200, 240], [400, 440]] - }]; - - request = spec.buildRequests(bidRequestData); - req_data = request[0].data; - - // then: - expect(req_data.w).to.equal(200); - expect(req_data.h).to.equal(240); - }); - it('validate_response_params', function () { let bidRequestData = { data: { diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index 4bdd657f419..4bc163aefe6 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -2,6 +2,7 @@ import {expect} from 'chai'; import {spec} from 'modules/outbrainBidAdapter.js'; import {config} from 'src/config.js'; import {server} from 'test/mocks/xhr'; +import { createEidsArray } from 'modules/userId/eids.js'; describe('Outbrain Adapter', function () { describe('Bid request and response', function () { @@ -204,7 +205,14 @@ describe('Outbrain Adapter', function () { request: JSON.stringify(expectedNativeAssets) } } - ] + ], + ext: { + prebid: { + channel: { + name: 'pbjs', version: '$prebid.version$' + } + } + } } const res = spec.buildRequests([bidRequest], commonBidderRequest) expect(res.url).to.equal('https://bidder-url.com') @@ -244,7 +252,14 @@ describe('Outbrain Adapter', function () { ] } } - ] + ], + ext: { + prebid: { + channel: { + name: 'pbjs', version: '$prebid.version$' + } + } + } } const res = spec.buildRequests([bidRequest], commonBidderRequest) expect(res.url).to.equal('https://bidder-url.com') @@ -330,6 +345,23 @@ describe('Outbrain Adapter', function () { config.resetConfig() }); + + it('should pass extended ids', function () { + let bidRequest = { + bidId: 'bidId', + params: {}, + userIdAsEids: createEidsArray({ + idl_env: 'id-value', + }), + ...commonBidRequest, + }; + + let res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data) + expect(resData.user.ext.eids).to.deep.equal([ + {source: 'liveramp.com', uids: [{id: 'id-value', atype: 3}]} + ]); + }); }) describe('interpretResponse', function () { diff --git a/test/spec/modules/outconBidAdapter_spec.js b/test/spec/modules/outconBidAdapter_spec.js deleted file mode 100644 index 81c3fdded62..00000000000 --- a/test/spec/modules/outconBidAdapter_spec.js +++ /dev/null @@ -1,100 +0,0 @@ -import { expect } from 'chai'; -import { spec } from '../../../modules/outconBidAdapter.js'; - -describe('outconBidAdapter', function () { - describe('bidRequestValidity', function () { - it('Check the bidRequest with pod param', function () { - expect(spec.isBidRequestValid({ - bidder: 'outcon', - params: { - pod: '5d603538eba7192ae14e39a4', - env: 'test' - } - })).to.equal(true); - }); - it('Check the bidRequest with internalID and publisherID params', function () { - expect(spec.isBidRequestValid({ - bidder: 'outcon', - params: { - internalId: '12345678', - publisher: '5d5d66f2306ea4114a37c7c2', - env: 'test' - } - })).to.equal(true); - }); - }); - describe('buildRequests', function () { - it('Build requests with pod param', function () { - expect(spec.buildRequests([{ - bidder: 'outcon', - params: { - pod: '5d603538eba7192ae14e39a4', - env: 'test' - } - }])).to.have.keys('method', 'url', 'data'); - }); - it('Build requests with internalID and publisherID params', function () { - expect(spec.buildRequests([{ - bidder: 'outcon', - params: { - internalId: '12345678', - publisher: '5d5d66f2306ea4114a37c7c2', - env: 'test' - } - }])).to.have.keys('method', 'url', 'data'); - }); - }); - describe('interpretResponse', function () { - const bidRequest = { - method: 'GET', - url: 'https://test.outcondigital.com/ad/', - data: { - pod: '5d603538eba7192ae14e39a4', - env: 'test', - vast: 'true' - } - }; - const bidResponse = { - body: { - cpm: 0.10, - cur: 'USD', - exp: 10, - creatives: [ - { - url: 'https://test.outcondigital.com/uploads/5d42e7a7306ea4689b67c122/frutas.mp4', - size: 3, - width: 1920, - height: 1080, - codec: 'video/mp4' - } - ], - ad: '5d6e6aef22063e392bf7f564', - type: 'video', - campaign: '5d42e44b306ea469593c76a2', - trackingURL: 'https://test.outcondigital.com/ad/track?track=5d6e6aef22063e392bf7f564', - vastURL: 'https://test.outcondigital.com/outcon.xml?impression=5d6e6aef22063e392bf7f564&demo=true' - }, - }; - it('check all the keys that are needed to interpret the response', function () { - const result = spec.interpretResponse(bidResponse, bidRequest); - let requiredKeys = [ - 'requestId', - 'cpm', - 'width', - 'height', - 'creativeId', - 'currency', - 'netRevenue', - 'ttl', - 'ad', - 'vastImpUrl', - 'mediaType', - 'vastUrl' - ]; - let resultKeys = Object.keys(result[0]); - resultKeys.forEach(function(key) { - expect(requiredKeys.indexOf(key) !== -1).to.equal(true); - }); - }) - }); -}); diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index 10b8ce31d28..658af310ea5 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -27,6 +27,20 @@ var validBidRequests = [ transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; +var validBidRequestsNoCustomData = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; var validBidRequestsMulti = [ { testId: 1, @@ -55,9 +69,6 @@ var validBidRequestsMulti = [ transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; -// use 'pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'criteoId', 'criteortus' -// NOTE THAT criteortus is no longer referenced anywhere - should be removed asap -// see http://prebid.org/dev-docs/modules/userId.html var validBidRequestsWithUserIdData = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -73,12 +84,12 @@ var validBidRequestsWithUserIdData = [ userId: { 'pubcid': '12345678', 'tdid': '1111tdid', - 'id5id': 'ID5-someId', - 'criteortus': {'ozone': {'userid': 'critId123'}}, + 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, 'criteoId': '1111criteoId', 'idl_env': 'liverampId', 'lipb': {'lipbid': 'lipbidId123'}, - 'parrableId': {'eid': '01.5678.parrableid'} + 'parrableId': {'eid': '01.5678.parrableid'}, + 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} }, userIdAsEids: [ { @@ -107,13 +118,6 @@ var validBidRequestsWithUserIdData = [ 'atype': 1, }] }, - { - 'source': 'criteortus', - 'uids': [{ - 'id': {'ozone': {'userid': 'critId123'}}, - 'atype': 1, - }] - }, { 'source': 'criteoId', 'uids': [{ @@ -285,7 +289,6 @@ var validBidRequests1OutstreamVideo2020 = [ } ]; -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var validBidderRequest1OutstreamVideo2020 = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -330,7 +333,7 @@ var validBidderRequest1OutstreamVideo2020 = { ] }, 'userId': { - 'id5id': 'ID5-ZHMOpSv9CkZNiNd1oR4zc62AzCgSS73fPjmQ6Od7OA', + 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' }, 'userIdAsEids': [ @@ -388,7 +391,6 @@ var validBidderRequest1OutstreamVideo2020 = { timeout: 3000 } }; -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var validBidderRequest = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -413,11 +415,6 @@ var validBidderRequest = { } }; -// bidder request with GDPR - change the values for testing: -// gdprConsent.gdprApplies (true/false) -// gdprConsent.vendorData.purposeConsents (make empty, make null, remove it) -// gdprConsent.vendorData.vendorConsents (remove 524, remove all, make the element null, remove it) -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var bidderRequestWithFullGdpr = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -506,7 +503,6 @@ var gdpr1 = { 'gdprApplies': true }; -// simulating the Mirror var bidderRequestWithPartialGdpr = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -552,7 +548,6 @@ var bidderRequestWithPartialGdpr = { } }; -// make sure the impid matches the request bidId var validResponse = { 'body': { 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', @@ -1107,7 +1102,6 @@ var multiRequest1 = [ } ]; -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var multiBidderRequest1 = { bidderRequest: { 'bidderCode': 'ozone', @@ -1501,7 +1495,6 @@ var multiResponse1 = { describe('ozone Adapter', function () { describe('isBidRequestValid', function () { - // A test ad unit that will consistently return test creatives let validBidReq = { bidder: BIDDER_CODE, params: { @@ -1935,7 +1928,6 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); expect(request).to.be.a('array'); expect(request[0]).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - // need to reset the singleRequest config flag: config.setConfig({'ozone': {'singleRequest': true}}); }); @@ -1959,7 +1951,6 @@ describe('ozone Adapter', function () { expect(payload.user.ext.consent).to.equal(consentString); }); - // mirror it('should add gdpr consent information to the request when vendorData is missing vendorConsents (Mirror)', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; let bidderRequest = validBidderRequest.bidderRequest; @@ -2012,16 +2003,14 @@ describe('ozone Adapter', function () { }; let bidRequests = validBidRequests; - // values from http://prebid.org/dev-docs/modules/userId.html#pubcommon-id bidRequests[0]['userId'] = { - 'criteortus': '1111', 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': '2222', + 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, 'idl_env': '3333', - 'lipb': {'lipbid': '4444'}, 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', 'pubcid': '5555', - 'tdid': '6666' + 'tdid': '6666', + 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} }; bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -2033,16 +2022,13 @@ describe('ozone Adapter', function () { it('should pick up the value of pubcid when built using the pubCommonId module (not userId)', function () { let bidRequests = validBidRequests; - // values from http://prebid.org/dev-docs/modules/userId.html#pubcommon-id bidRequests[0]['userId'] = { - 'criteortus': '1111', 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': '2222', + 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, 'idl_env': '3333', - 'lipb': {'lipbid': '4444'}, 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', - // 'pubcid': '5555', // remove pubcid from here to emulate the OLD module & cause the failover code to kick in - 'tdid': '6666' + 'tdid': '6666', + 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} }; bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; const request = spec.buildRequests(bidRequests, validBidderRequest.bidderRequest); @@ -2056,11 +2042,9 @@ describe('ozone Adapter', function () { /* 'pubcid': '12345678', 'tdid': '1111tdid', - 'id5id': 'ID5-someId', - 'criteortus': {'ozone': {'userid': 'critId123'}}, + 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, 'criteoId': '1111criteoId', 'idl_env': 'liverampId', - 'lipb': {'lipbid': 'lipbidId123'}, 'parrableId': {'eid': '01.5678.parrableid'} */ @@ -2074,16 +2058,14 @@ describe('ozone Adapter', function () { expect(payload.user.ext.eids[1]['uids'][0]['id']).to.equal('1111tdid'); expect(payload.user.ext.eids[2]['source']).to.equal('id5-sync.com'); expect(payload.user.ext.eids[2]['uids'][0]['id']).to.equal('ID5-someId'); - expect(payload.user.ext.eids[3]['source']).to.equal('criteortus'); // this is deprecated - expect(payload.user.ext.eids[3]['uids'][0]['id']['ozone']['userid']).to.equal('critId123'); - expect(payload.user.ext.eids[4]['source']).to.equal('criteoId'); - expect(payload.user.ext.eids[4]['uids'][0]['id']).to.equal('1111criteoId'); - expect(payload.user.ext.eids[5]['source']).to.equal('idl_env'); - expect(payload.user.ext.eids[5]['uids'][0]['id']).to.equal('liverampId'); - expect(payload.user.ext.eids[6]['source']).to.equal('lipb'); - expect(payload.user.ext.eids[6]['uids'][0]['id']['lipbid']).to.equal('lipbidId123'); - expect(payload.user.ext.eids[7]['source']).to.equal('parrableId'); - expect(payload.user.ext.eids[7]['uids'][0]['id']['eid']).to.equal('01.5678.parrableid'); + expect(payload.user.ext.eids[3]['source']).to.equal('criteoId'); + expect(payload.user.ext.eids[3]['uids'][0]['id']).to.equal('1111criteoId'); + expect(payload.user.ext.eids[4]['source']).to.equal('idl_env'); + expect(payload.user.ext.eids[4]['uids'][0]['id']).to.equal('liverampId'); + expect(payload.user.ext.eids[5]['source']).to.equal('lipb'); + expect(payload.user.ext.eids[5]['uids'][0]['id']['lipbid']).to.equal('lipbidId123'); + expect(payload.user.ext.eids[6]['source']).to.equal('parrableId'); + expect(payload.user.ext.eids[6]['uids'][0]['id']['eid']).to.equal('01.5678.parrableid'); }); it('replaces the auction url for a config override', function () { @@ -2093,6 +2075,21 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); expect(request.url).to.equal(fakeOrigin + '/openrtb2/auction'); expect(request.method).to.equal('POST'); + const data = JSON.parse(request.data); + expect(data.ext.ozone.origin).to.equal(fakeOrigin); + config.setConfig({'ozone': {'kvpPrefix': null, 'endpointOverride': null}}); + spec.propertyBag.whitelabel = null; + }); + + it('replaces the FULL auction url for a config override', function () { + spec.propertyBag.whitelabel = null; + let fakeurl = 'http://sometestendpoint/myfullurl'; + config.setConfig({'ozone': {'endpointOverride': {'auctionUrl': fakeurl}}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + expect(request.url).to.equal(fakeurl); + expect(request.method).to.equal('POST'); + const data = JSON.parse(request.data); + expect(data.ext.ozone.origin).to.equal(fakeurl); config.setConfig({'ozone': {'kvpPrefix': null, 'endpointOverride': null}}); spec.propertyBag.whitelabel = null; }); @@ -2109,6 +2106,28 @@ describe('ozone Adapter', function () { config.setConfig({'ozone': {'kvpPrefix': null, 'endpointOverride': null}}); spec.propertyBag.whitelabel = null; }); + it('should generate all the adservertargeting keys correctly named', function () { + var specMock = utils.deepClone(spec); + config.setConfig({'ozone': {'kvpPrefix': 'xx'}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(result[0].adserverTargeting).to.have.own.property('xx_appnexus_crid'); + expect(utils.deepAccess(result[0].adserverTargeting, 'xx_appnexus_crid')).to.equal('98493581'); + expect(utils.deepAccess(result[0].adserverTargeting, 'xx_pb')).to.equal(0.5); + expect(utils.deepAccess(result[0].adserverTargeting, 'xx_adId')).to.equal('2899ec066a91ff8-0-xx-0'); + expect(utils.deepAccess(result[0].adserverTargeting, 'xx_size')).to.equal('300x600'); + expect(utils.deepAccess(result[0].adserverTargeting, 'xx_pb_r')).to.equal('0.50'); + expect(utils.deepAccess(result[0].adserverTargeting, 'xx_bid')).to.equal('true'); + config.resetConfig(); + }); + it('should create a meta object on each bid returned', function () { + var specMock = utils.deepClone(spec); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(result[0]).to.have.own.property('meta'); + expect(result[0].meta.advertiserDomains[0]).to.equal('http://prebid.org'); + config.resetConfig(); + }); it('replaces the kvp prefix ', function () { spec.propertyBag.whitelabel = null; @@ -2131,9 +2150,8 @@ describe('ozone Adapter', function () { config.setConfig({'lmc': {'kvpPrefix': null}}); // I cant remove the key so set the value to null spec.propertyBag.whitelabel = null; }); - var specMock = utils.deepClone(spec); it('should use oztestmode GET value if set', function() { - // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: + var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; @@ -2142,8 +2160,32 @@ describe('ozone Adapter', function () { expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(data.imp[0].ext.ozone.customData[0].targeting.oztestmode).to.equal('mytestvalue_123'); }); + it('should pass through GET params if present: ozf, ozpf, ozrp, ozip', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {ozf: '1', ozpf: '0', ozrp: '2', ozip: '123'}; + }; + const request = specMock.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.ozone.ozf).to.equal(1); + expect(data.ext.ozone.ozpf).to.equal(0); + expect(data.ext.ozone.ozrp).to.equal(2); + expect(data.ext.ozone.ozip).to.equal(123); + }); + it('should pass through GET params if present: ozf, ozpf, ozrp, ozip with alternative values', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {ozf: 'false', ozpf: 'true', ozrp: 'xyz', ozip: 'hello'}; + }; + const request = specMock.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.ozone.ozf).to.equal(0); + expect(data.ext.ozone.ozpf).to.equal(1); + expect(data.ext.ozone).to.not.haveOwnProperty('ozrp'); + expect(data.ext.ozone).to.not.haveOwnProperty('ozip'); + }); it('should use oztestmode GET value if set, even if there is no customdata in config', function() { - // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: + var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; @@ -2152,8 +2194,29 @@ describe('ozone Adapter', function () { expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(data.imp[0].ext.ozone.customData[0].targeting.oztestmode).to.equal('mytestvalue_123'); }); + it('should use GET values auction=dev & cookiesync=dev if set', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {}; + }; + let request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); + let url = request.url; + expect(url).to.equal('https://elb.the-ozone-project.com/openrtb2/auction'); + let cookieUrl = specMock.getCookieSyncUrl(); + expect(cookieUrl).to.equal('https://elb.the-ozone-project.com/static/load-cookie.html'); + + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'auction': 'dev', 'cookiesync': 'dev'}; + }; + request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); + url = request.url; + expect(url).to.equal('https://test.ozpr.net/openrtb2/auction'); + cookieUrl = specMock.getCookieSyncUrl(); + expect(cookieUrl).to.equal('https://test.ozpr.net/static/load-cookie.html'); + }); it('should use a valid ozstoredrequest GET value if set to override the placementId values, and set oz_rw if we find it', function() { - // mock the getGetParametersAsObject function to simulate GET parameters for ozstoredrequest: + var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {'ozstoredrequest': '1122334455'}; // 10 digits are valid }; @@ -2163,7 +2226,7 @@ describe('ozone Adapter', function () { expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1122334455'); }); it('should NOT use an invalid ozstoredrequest GET value if set to override the placementId values, and set oz_rw to 0', function() { - // mock the getGetParametersAsObject function to simulate GET parameters for ozstoredrequest: + var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {'ozstoredrequest': 'BADVAL'}; // 10 digits are valid }; @@ -2230,6 +2293,72 @@ describe('ozone Adapter', function () { const payload = JSON.parse(request.data); expect(payload.ext.ozone.oz_kvp_rw).to.equal(0); }); + it('should handle ortb2 site data', function () { + config.setConfig({'ortb2': { + 'site': { + 'name': 'example_ortb2_name', + 'domain': 'page.example.com', + 'cat': ['IAB2'], + 'sectioncat': ['IAB2-2'], + 'pagecat': ['IAB2-2'], + 'page': 'https://page.example.com/here.html', + 'ref': 'https://ref.example.com', + 'keywords': 'power tools, drills', + 'search': 'drill' + } + }}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.imp[0].ext.ozone.customData[0].targeting.name).to.equal('example_ortb2_name'); + expect(payload.user.ext).to.not.have.property('gender'); + config.resetConfig(); + }); + it('should add ortb2 site data when there is no customData already created', function () { + config.setConfig({'ortb2': { + 'site': { + 'name': 'example_ortb2_name', + 'domain': 'page.example.com', + 'cat': ['IAB2'], + 'sectioncat': ['IAB2-2'], + 'pagecat': ['IAB2-2'], + 'page': 'https://page.example.com/here.html', + 'ref': 'https://ref.example.com', + 'keywords': 'power tools, drills', + 'search': 'drill' + } + }}); + const request = spec.buildRequests(validBidRequestsNoCustomData, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.imp[0].ext.ozone.customData[0].targeting.name).to.equal('example_ortb2_name'); + expect(payload.imp[0].ext.ozone.customData[0].targeting).to.not.have.property('gender') + config.resetConfig(); + }); + it('should add ortb2 user data to the user object', function () { + config.setConfig({'ortb2': { + 'user': { + 'gender': 'who knows these days' + } + }}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.user.gender).to.equal('who knows these days'); + config.resetConfig(); + }); + it('should not override the user.ext.consent string even if this is set in config ortb2', function () { + config.setConfig({'ortb2': { + 'user': { + 'ext': { + 'consent': 'this is the consent override that shouldnt work', + 'consent2': 'this should be set' + } + } + }}); + const request = spec.buildRequests(validBidRequests, bidderRequestWithFullGdpr.bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.user.ext.consent2).to.equal('this should be set'); + expect(payload.user.ext.consent).to.equal('BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA'); + config.resetConfig(); + }); it('should have openrtb video params', function() { let allowed = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext']; const request = spec.buildRequests(validBidRequests1OutstreamVideo2020, validBidderRequest.bidderRequest); @@ -2241,6 +2370,53 @@ describe('ozone Adapter', function () { } expect(payload.imp[0].video.ext).to.include({'context': 'outstream'}); }); + it('should handle standard floor config correctly', function () { + config.setConfig({ + floors: { + enforcement: { + floorDeals: false, + bidAdjustment: true + }, + data: { + currency: 'USD', + schema: { + fields: ['mediaType'] + }, + values: { + 'video': 1.20, + 'banner': 0.8 + } + } + } + }); + let localBidRequest = JSON.parse(JSON.stringify(validBidRequestsWithBannerMediaType)); + localBidRequest[0].getFloor = function(x) { return {'currency': 'USD', 'floor': 0.8} }; + const request = spec.buildRequests(localBidRequest, validBidderRequest.bidderRequest); + const payload = JSON.parse(request.data); + expect(utils.deepAccess(payload, 'imp.0.floor.banner.currency')).to.equal('USD'); + expect(utils.deepAccess(payload, 'imp.0.floor.banner.floor')).to.equal(0.8); + config.resetConfig(); + }); + + it('handles schain object in each bidrequest (will be the same in each br)', function () { + let br = JSON.parse(JSON.stringify(validBidRequests)); + let schainConfigObject = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'bidderA.com', + 'sid': '00001', + 'hp': 1 + } + ] + }; + br[0]['schain'] = schainConfigObject; + const request = spec.buildRequests(br, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.source.ext).to.haveOwnProperty('schain'); + expect(data.source.ext.schain).to.deep.equal(schainConfigObject); // .deep.equal() : Target object deeply (but not strictly) equals `{a: 1}` + }); }); describe('interpretResponse', function () { @@ -2266,11 +2442,20 @@ describe('ozone Adapter', function () { const result = spec.interpretResponse(validResponse, request); expect(result.length).to.equal(1); }); + it('should build bid array with usp/CCPA', function () { + let validBR = JSON.parse(JSON.stringify(bidderRequestWithFullGdpr.bidderRequest)); + validBR.uspConsent = '1YNY'; + const request = spec.buildRequests(validBidRequests, validBR); + const payload = JSON.parse(request.data); + expect(payload.user.ext.uspConsent).to.equal('1YNY'); + }); it('should build bid array with only partial gdpr', function () { var validBidderRequestWithGdpr = bidderRequestWithPartialGdpr.bidderRequest; validBidderRequestWithGdpr.gdprConsent = {'gdprApplies': 1, 'consentString': 'This is the gdpr consent string'}; const request = spec.buildRequests(validBidRequests, validBidderRequestWithGdpr); + const payload = JSON.parse(request.data); + expect(payload.user.ext.consent).to.be.a('string'); }); it('should fail ok if no seatbid in server response', function () { @@ -2374,7 +2559,7 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const result = spec.interpretResponse(validResponse, request); expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_adv')).to.be.undefined; - expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_adId')).to.equal('2899ec066a91ff8-0-0'); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_adId')).to.equal('2899ec066a91ff8-0-oz-0'); config.resetConfig(); }); it('should ignore a whitelist if enhancedAdserverTargeting is false', function () { @@ -2421,7 +2606,7 @@ describe('ozone Adapter', function () { const result = spec.interpretResponse(validres, request); expect(result.length).to.equal(1); expect(result[0]['price']).to.equal(0.9); - expect(result[0]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('2899ec066a91ff8-0-1'); + expect(result[0]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('2899ec066a91ff8-0-oz-1'); }); it('should correctly process an auction with 2 adunits & multiple bidders one of which bids for both adslots', function() { let validres = JSON.parse(JSON.stringify(multiResponse1)); @@ -2431,8 +2616,7 @@ describe('ozone Adapter', function () { expect(result[1]['price']).to.equal(0.521); expect(result[1]['impid']).to.equal('3025f169863b7f8'); expect(result[1]['id']).to.equal('18552976939844999'); - expect(result[1]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('3025f169863b7f8-0-2'); - // change the bid values so a different second bid for an impid by the same bidder gets dropped + expect(result[1]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('3025f169863b7f8-0-oz-2'); validres = JSON.parse(JSON.stringify(multiResponse1)); validres.body.seatbid[0].bid[1].price = 1.1; validres.body.seatbid[0].bid[1].cpm = 1.1; @@ -2441,7 +2625,7 @@ describe('ozone Adapter', function () { expect(result[1]['price']).to.equal(1.1); expect(result[1]['impid']).to.equal('3025f169863b7f8'); expect(result[1]['id']).to.equal('18552976939844681'); - expect(result[1]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('3025f169863b7f8-0-1'); + expect(result[1]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('3025f169863b7f8-0-oz-1'); }); }); @@ -2455,7 +2639,6 @@ describe('ozone Adapter', function () { expect(result).to.be.empty; }); it('should append the various values if they exist', function() { - // get the cookie bag populated spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1); expect(result).to.be.an('array'); @@ -2464,6 +2647,18 @@ describe('ozone Adapter', function () { expect(result[0].url).to.include('gdpr=1'); expect(result[0].url).to.include('gdpr_consent=BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA'); }); + it('should append ccpa (usp data)', function() { + spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1, '1YYN'); + expect(result).to.be.an('array'); + expect(result[0].url).to.include('usp_consent=1YYN'); + }); + it('should use "" if no usp is sent to cookieSync', function() { + spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1); + expect(result).to.be.an('array'); + expect(result[0].url).to.include('usp_consent=&'); + }); }); describe('video object utils', function () { @@ -2681,4 +2876,27 @@ describe('ozone Adapter', function () { expect(response[1].bid.length).to.equal(2); }); }); + /** + * spec.getWhitelabelConfigItem test - get a config value for a whitelabelled bidder, + * from a standard ozone.oz_xxxx_yyy string + */ + describe('getWhitelabelConfigItem', function() { + it('should fetch the whitelabelled equivalent config value correctly', function () { + var specMock = utils.deepClone(spec); + config.setConfig({'ozone': {'oz_omp_floor': 'ozone-floor-value'}}); + config.setConfig({'markbidder': {'mb_omp_floor': 'markbidder-floor-value'}}); + specMock.propertyBag.whitelabel = {bidder: 'ozone', keyPrefix: 'oz'}; + let testKey = 'ozone.oz_omp_floor'; + let ozone_value = specMock.getWhitelabelConfigItem(testKey); + expect(ozone_value).to.equal('ozone-floor-value'); + specMock.propertyBag.whitelabel = {bidder: 'markbidder', keyPrefix: 'mb'}; + let markbidder_config = specMock.getWhitelabelConfigItem(testKey); + expect(markbidder_config).to.equal('markbidder-floor-value'); + config.setConfig({'markbidder': {'singleRequest': 'markbidder-singlerequest-value'}}); + let testKey2 = 'ozone.singleRequest'; + let markbidder_config2 = specMock.getWhitelabelConfigItem(testKey2); + expect(markbidder_config2).to.equal('markbidder-singlerequest-value'); + config.resetConfig(); + }); + }); }); diff --git a/test/spec/modules/papyrusBidAdapter_spec.js b/test/spec/modules/papyrusBidAdapter_spec.js deleted file mode 100644 index 20fcced2950..00000000000 --- a/test/spec/modules/papyrusBidAdapter_spec.js +++ /dev/null @@ -1,115 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/papyrusBidAdapter.js'; - -const ENDPOINT = 'https://prebid.papyrus.global'; -const BIDDER_CODE = 'papyrus'; - -describe('papyrus Adapter', function () { - describe('isBidRequestValid', function () { - let validBidReq = { - bidder: BIDDER_CODE, - params: { - address: '0xd7e2a771c5dcd5df7f789477356aecdaeee6c985', - placementId: 'b57e55fd18614b0591893e9fff41fbea' - } - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(validBidReq)).to.equal(true); - }); - - let invalidBidReq = { - bidder: BIDDER_CODE, - params: { - 'placementId': '' - } - }; - - it('should not validate incorrect bid request', function () { - expect(spec.isBidRequestValid(invalidBidReq)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let bidRequests = [ - { - bidder: BIDDER_CODE, - params: { - address: '0xd7e2a771c5dcd5df7f789477356aecdaeee6c985', - placementId: 'b57e55fd18614b0591893e9fff41fbea' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(bidRequests); - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('POST'); - }); - - it('sends valid bids count', function () { - const request = spec.buildRequests(bidRequests); - expect(request.data.length).to.equal(1); - }); - - it('sends all bid parameters', function () { - const request = spec.buildRequests(bidRequests); - expect(request.data[0]).to.have.all.keys(['address', 'placementId', 'sizes', 'bidId', 'transactionId']); - }); - - it('sedns valid sizes parameter', function () { - const request = spec.buildRequests(bidRequests); - expect(request.data[0].sizes[0]).to.equal('300x250'); - }); - }); - - describe('interpretResponse', function () { - let bidRequests = [ - { - bidder: BIDDER_CODE, - params: { - address: '0xd7e2a771c5dcd5df7f789477356aecdaeee6c985', - placementId: 'b57e55fd18614b0591893e9fff41fbea' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - let bidResponse = { - bids: [ - { - id: '1036e9746c-d186-49ae-90cb-2796d0f9b223', - adm: 'test adm', - cpm: 100, - height: 250, - width: 300 - } - ] - }; - - it('should build bid array', function () { - const request = spec.buildRequests(bidRequests); - const result = spec.interpretResponse({body: bidResponse}, request[0]); - expect(result.length).to.equal(1); - }); - - it('should have all relevant fields', function () { - const request = spec.buildRequests(bidRequests); - const result = spec.interpretResponse({body: bidResponse}, request[0]); - const bid = result[0]; - - expect(bid.cpm).to.equal(bidResponse.bids[0].cpm); - expect(bid.width).to.equal(bidResponse.bids[0].width); - expect(bid.height).to.equal(bidResponse.bids[0].height); - }); - }); -}); diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 3f517a66c68..44118fb50de 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -12,6 +12,7 @@ import { server } from 'test/mocks/xhr.js'; const storage = newStorageManager(); const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; +const EXPIRE_COOKIE_TIME = 864000000; const P_COOKIE_NAME = '_parrable_id'; const P_COOKIE_EID = '01.1563917337.test-eid'; const P_XHR_EID = '01.1588030911.test-new-eid' @@ -21,6 +22,7 @@ const P_CONFIG_MOCK = { partners: 'parrable_test_partner_123,parrable_test_partner_456' } }; +const RESPONSE_HEADERS = { 'Content-Type': 'application/json' }; function getConfigMock() { return { @@ -57,6 +59,15 @@ function serializeParrableId(parrableId) { if (parrableId.ccpaOptout) { str += ',ccpaOptout:1'; } + if (parrableId.tpc !== undefined) { + const tpcSupportComponent = parrableId.tpc === true ? 'tpc:1' : 'tpc:0'; + str += `,${tpcSupportComponent}`; + str += `,tpcUntil:${parrableId.tpcUntil}`; + } + if (parrableId.filteredUntil) { + str += `,filteredUntil:${parrableId.filteredUntil}`; + str += `,filterHits:${parrableId.filterHits}`; + } return str; } @@ -65,7 +76,7 @@ function writeParrableCookie(parrableId) { storage.setCookie( P_COOKIE_NAME, cookieValue, - (new Date(Date.now() + 5000).toUTCString()), + (new Date(Date.now() + EXPIRE_COOKIE_TIME).toUTCString()), 'lax' ); } @@ -125,7 +136,6 @@ describe('Parrable ID System', function() { { 'Content-Type': 'text/plain' }, JSON.stringify({ eid: P_XHR_EID }) ); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({ eid: P_XHR_EID }); @@ -242,6 +252,172 @@ describe('Parrable ID System', function() { }) }); }); + + describe('third party cookie support', function () { + let logErrorStub; + let callbackSpy = sinon.spy(); + + beforeEach(function() { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + callbackSpy.resetHistory(); + removeParrableCookie(); + }); + + afterEach(function() { + logErrorStub.restore(); + }); + + describe('when getting tpcSupport from XHR response', function () { + let request; + let dateNowStub; + const dateNowMock = Date.now(); + const tpcSupportTtl = 1; + + before(() => { + dateNowStub = sinon.stub(Date, 'now').returns(dateNowMock); + }); + + after(() => { + dateNowStub.restore(); + }); + + it('should set tpcSupport: true and tpcUntil in the cookie', function () { + let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); + callback(callbackSpy); + request = server.requests[0]; + + request.respond( + 200, + RESPONSE_HEADERS, + JSON.stringify({ eid: P_XHR_EID, tpcSupport: true, tpcSupportTtl }) + ); + + expect(storage.getCookie(P_COOKIE_NAME)).to.equal( + encodeURIComponent('eid:' + P_XHR_EID + ',tpc:1,tpcUntil:' + Math.floor((dateNowMock / 1000) + tpcSupportTtl)) + ); + }); + + it('should set tpcSupport: false and tpcUntil in the cookie', function () { + let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); + callback(callbackSpy); + request = server.requests[0]; + request.respond( + 200, + RESPONSE_HEADERS, + JSON.stringify({ eid: P_XHR_EID, tpcSupport: false, tpcSupportTtl }) + ); + + expect(storage.getCookie(P_COOKIE_NAME)).to.equal( + encodeURIComponent('eid:' + P_XHR_EID + ',tpc:0,tpcUntil:' + Math.floor((dateNowMock / 1000) + tpcSupportTtl)) + ); + }); + + it('should not set tpcSupport in the cookie', function () { + let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); + callback(callbackSpy); + request = server.requests[0]; + + request.respond( + 200, + RESPONSE_HEADERS, + JSON.stringify({ eid: P_XHR_EID }) + ); + + expect(storage.getCookie(P_COOKIE_NAME)).to.equal( + encodeURIComponent('eid:' + P_XHR_EID) + ); + }); + }); + }); + + describe('request-filter status', function () { + let logErrorStub; + let callbackSpy = sinon.spy(); + + beforeEach(function() { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + callbackSpy.resetHistory(); + removeParrableCookie(); + }); + + afterEach(function() { + logErrorStub.restore(); + }); + + describe('when getting filterTtl from XHR response', function () { + let request; + let dateNowStub; + const dateNowMock = Date.now(); + const filterTtl = 1000; + + before(() => { + dateNowStub = sinon.stub(Date, 'now').returns(dateNowMock); + }); + + after(() => { + dateNowStub.restore(); + }); + + it('should set filteredUntil in the cookie', function () { + let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); + callback(callbackSpy); + request = server.requests[0]; + + request.respond( + 200, + RESPONSE_HEADERS, + JSON.stringify({ eid: P_XHR_EID, filterTtl }) + ); + + expect(storage.getCookie(P_COOKIE_NAME)).to.equal( + encodeURIComponent( + 'eid:' + P_XHR_EID + + ',filteredUntil:' + Math.floor((dateNowMock / 1000) + filterTtl) + + ',filterHits:0') + ); + }); + + it('should increment filterHits in the cookie', function () { + writeParrableCookie({ + eid: P_XHR_EID, + filteredUntil: Math.floor((dateNowMock / 1000) + filterTtl), + filterHits: 0 + }); + let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); + callback(callbackSpy); + + expect(storage.getCookie(P_COOKIE_NAME)).to.equal( + encodeURIComponent( + 'eid:' + P_XHR_EID + + ',filteredUntil:' + Math.floor((dateNowMock / 1000) + filterTtl) + + ',filterHits:1') + ); + }); + + it('should send filterHits in the XHR', function () { + const filterHits = 1; + writeParrableCookie({ + eid: P_XHR_EID, + filteredUntil: Math.floor(dateNowMock / 1000), + filterHits + }); + let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); + callback(callbackSpy); + request = server.requests[0]; + + let queryParams = utils.parseQS(request.url.split('?')[1]); + let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); + + expect(data.filterHits).to.equal(filterHits); + }); + }); + }); }); describe('parrableIdSystem.decode()', function() { @@ -529,7 +705,7 @@ describe('Parrable ID System', function() { }); }); - describe('partners parsing', () => { + describe('partners parsing', function () { let callbackSpy = sinon.spy(); const partnersTestCase = [ diff --git a/test/spec/modules/performaxBidAdapter_spec.js b/test/spec/modules/performaxBidAdapter_spec.js deleted file mode 100644 index 43c256b9d13..00000000000 --- a/test/spec/modules/performaxBidAdapter_spec.js +++ /dev/null @@ -1,274 +0,0 @@ -import * as utils from 'src/utils.js'; -import { expect } from 'chai'; -import { spec } from 'modules/performaxBidAdapter'; - -describe('PerformaxAdapter', function () { - let bidRequests, bidderRequest; - let serverResponse, serverRequest; - - const URL = - 'https://dale.performax.cz/hb?slotId[]=3,2&client=hellboy:v0.0.1&auctionId=144b5079-8cbf-49a5-aca7-a68b3296cd6c'; - - bidRequests = [ - { - adUnitCode: 'postbid_iframe', - auctionId: '144b5079-8cbf-49a5-aca7-a68b3296cd6c', - bidId: '2a4332f6b2bc74', - bidRequestsCount: 1, - bidder: 'performax', - bidderRequestId: '1c7d8bf204f11e', - bidderRequestsCount: 1, - bidderWinsCount: 0, - mediaTypes: { - banner: { - sizes: [[300, 300]], - }, - }, - params: { - slotId: 3, - }, - sizes: [[300, 300]], - src: 'client', - transactionId: '14969d09-0068-4d5b-a34e-e35091561dee', - }, - { - adUnitCode: 'postbid_iframe2', - auctionId: '144b5079-8cbf-49a5-aca7-a68b3296cd6c', - bidId: '300bb0ac6a156a', - bidRequestsCount: 1, - bidder: 'performax', - bidderRequestId: '1c7d8bf204f11e', - bidderRequestsCount: 1, - bidderWinsCount: 0, - mediaTypes: { - banner: { - sizes: [[300, 300]], - }, - }, - params: { - slotId: 2, - }, - sizes: [[300, 300]], - src: 'client', - transactionId: '107cbebd-8c36-4456-b28c-91a19ba80151', - }, - ]; - - bidderRequest = { - auctionId: '144b5079-8cbf-49a5-aca7-a68b3296cd6c', - auctionStart: 1594281941845, - bidderCode: 'performax', - bidderRequestId: '1c7d8bf204f11e', - bids: bidRequests, - refererInfo: { - canonicalUrl: '', - numIframes: 0, - reachedTop: true, - referer: '', - }, - stack: [''], - start: 1594281941935, - timeout: 3600, - }; - - serverResponse = { - body: [ - { - ad: { - code: '$SYS_ID$ $VAR_NAME$ rest of the code', - data: { - SYS_ID: 1, - VAR_NAME: 'name', - }, - format_id: 2, - id: 11, - size: { - width: 300, - height: 300, - }, - tag_ids: [], - type: 'creative', - }, - cpm: 30, - creativeId: 'creative:11', - currency: 'CZK', - height: 300, - meta: { - agencyId: 1, - mediaType: 'banner', - }, - netRevenue: true, - requestId: '2a4332f6b2bc74', - ttl: 60, - width: 300, - }, - { - ad: { - code: '', - reason: 'Slot 2 does not allow HB requests', - type: 'empty', - }, - cpm: 0, - creativeId: null, - currency: 'CZK', - height: null, - meta: { - agencyId: null, - mediaType: 'banner', - }, - netRevenue: true, - requestId: '1c7d8bf204f11e', - ttl: 60, - width: 300, - }, - ], - }; - - serverRequest = { - data: { - bidderRequest: bidderRequest, - validBidRequests: bidRequests, - }, - method: 'POST', - options: { - contentType: 'application/json', - }, - url: URL, - }; - - describe('Bid validations', function () { - it('Valid bid', function () { - let validBid = { - bidder: 'performax', - params: { - slotId: 2, - }, - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); - }); - - it('Invalid bid: required param is missing', function () { - let invalidBid = { - bidder: 'performax', - params: { - invalidParam: 2, - }, - }, - isValid = spec.isBidRequestValid(invalidBid); - expect(isValid).to.equal(false); - }); - }); - - describe('Build Url', function () { - it('Should return url', function () { - let url = spec.buildUrl(bidRequests, bidderRequest); - expect(url).to.equal(URL); - }); - }); - - describe('Build Request', function () { - it('Should not modify bidRequests and bidder Requests', function () { - let originalBidRequests = utils.deepClone(bidRequests); - let originalBidderRequest = utils.deepClone(bidderRequest); - let request = spec.buildRequests(bidRequests, bidderRequest); - - expect(bidRequests).to.deep.equal(originalBidRequests); - expect(bidderRequest).to.deep.equal(originalBidderRequest); - }); - - it('Endpoint checking', function () { - let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.equal(URL); - expect(request.method).to.equal('POST'); - expect(request.options).to.deep.equal({ - contentType: 'application/json', - }); - }); - - it('Request params checking', function () { - let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.validBidRequests).to.deep.equal(bidRequests); - expect(request.data.bidderRequest).to.deep.equal(bidderRequest); - }); - }); - - describe('Build Html', function () { - it('Ad with data: should return build html', function () { - let validAd = { - code: '$SYS_ID$ $VAR_NAME$ rest of the code', - data: { - SYS_ID: 1, - VAR_NAME: 'name', - }, - format_id: 2, - id: 11, - size: { - width: 300, - height: 300, - }, - tag_ids: [], - type: 'creative', - }; - let html = spec.buildHtml(validAd); - expect(html).to.equal('1 name rest of the code'); - }); - - it('Ad with partial data: should return html without data change', function () { - let adWithPartialData = { - code: '$SYS_ID$ $VAR_NAME$ rest of the code', - data: { - VAR_NAME: 'name', - }, - format_id: 2, - id: 11, - size: { - width: 300, - height: 300, - }, - tag_ids: [], - type: 'creative', - }; - let html = spec.buildHtml(adWithPartialData); - expect(html).to.equal('$SYS_ID$ name rest of the code'); - }); - - it('Ad without data: should return html without data change', function () { - let adWithoutData = { - code: '$SYS_ID$ $VAR_NAME$ rest of the code', - format_id: 2, - id: 11, - size: { - width: 300, - height: 300, - }, - tag_ids: [], - type: 'creative', - }; - let html = spec.buildHtml(adWithoutData); - expect(html).to.equal('$SYS_ID$ $VAR_NAME$ rest of the code'); - }); - }); - - describe('Interpret Response', function () { - it('Ad without data: should return html without data change', function () { - let ads = spec.interpretResponse(serverResponse, serverRequest); - expect(ads).to.have.length(1); - expect(ads[0]).to.deep.equal({ - ad: '1 name rest of the code', - cpm: 30, - creativeId: 'creative:11', - currency: 'CZK', - height: 300, - meta: { - agencyId: 1, - mediaType: 'banner', - }, - netRevenue: true, - requestId: '2a4332f6b2bc74', - ttl: 60, - width: 300, - }); - }); - }); -}); diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index cf1f3861eab..7cf6b66f839 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -1,15 +1,26 @@ -import { permutiveSubmodule, storage, getSegments, initSegments, isAcEnabled, isPermutiveOnPage } from 'modules/permutiveRtdProvider.js' +import { + permutiveSubmodule, + storage, + getSegments, + initSegments, + isAcEnabled, + isPermutiveOnPage, + setBidderRtb +} from 'modules/permutiveRtdProvider.js' import { deepAccess } from '../../../src/utils.js' +import { config } from 'src/config.js' describe('permutiveRtdProvider', function () { before(function () { const data = getTargetingData() setLocalStorage(data) + config.resetConfig() }) after(function () { const data = getTargetingData() removeLocalStorage(data) + config.resetConfig() }) describe('permutiveSubmodule', function () { @@ -18,6 +29,65 @@ describe('permutiveRtdProvider', function () { }) }) + describe('ortb2 config', function () { + beforeEach(function () { + config.resetConfig() + }) + + it('should add ortb2 config', function () { + const moduleConfig = getConfig() + const bidderConfig = config.getBidderConfig() + const acBidders = moduleConfig.params.acBidders + const expectedTargetingData = transformedTargeting().ac.map(seg => { + return { id: seg } + }) + + setBidderRtb({}, moduleConfig) + + acBidders.forEach(bidder => { + expect(bidderConfig[bidder].ortb2.user.data).to.deep.include.members([{ + name: 'permutive.com', + segment: expectedTargetingData + }]) + }) + }) + it('should not overwrite ortb2 config', function () { + const moduleConfig = getConfig() + const bidderConfig = config.getBidderConfig() + const acBidders = moduleConfig.params.acBidders + const sampleOrtbConfig = { + ortb2: { + site: { + name: 'example' + }, + user: { + keywords: 'a,b', + data: [ + { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ id: '687' }, { id: '123' }] + } + ] + } + } + } + + config.setBidderConfig({ + bidders: acBidders, + config: sampleOrtbConfig + }) + + setBidderRtb({}, moduleConfig) + + acBidders.forEach(bidder => { + expect(bidderConfig[bidder].ortb2.site.name).to.equal(sampleOrtbConfig.ortb2.site.name) + expect(bidderConfig[bidder].ortb2.user.keywords).to.equal(sampleOrtbConfig.ortb2.user.keywords) + expect(bidderConfig[bidder].ortb2.user.data).to.deep.include.members([sampleOrtbConfig.ortb2.user.data[0]]) + }) + }) + }) + describe('Getting segments', function () { it('should retrieve segments in the expected structure', function () { const data = transformedTargeting() @@ -93,25 +163,6 @@ describe('permutiveRtdProvider', function () { }) } }) - it('sets segment targeting for TrustX', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() - - initSegments({ adUnits }, callback, config) - - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid - - if (bidder === 'trustx') { - expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac) - } - }) - }) - } - }) }) describe('Custom segment targeting', function () { @@ -291,11 +342,11 @@ function getTargetingData () { } function getAdUnits () { - const div_1_sizes = [ + const div1sizes = [ [300, 250], [300, 600] ] - const div_2_sizes = [ + const div2sizes = [ [728, 90], [970, 250] ] @@ -304,7 +355,7 @@ function getAdUnits () { code: '/19968336/header-bid-tag-0', mediaTypes: { banner: { - sizes: div_1_sizes + sizes: div1sizes } }, bids: [ @@ -363,7 +414,7 @@ function getAdUnits () { code: '/19968336/header-bid-tag-1', mediaTypes: { banner: { - sizes: div_2_sizes + sizes: div2sizes } }, bids: [ diff --git a/test/spec/modules/pilotxBidAdapter_spec.js b/test/spec/modules/pilotxBidAdapter_spec.js new file mode 100644 index 00000000000..2ef31c0a8f5 --- /dev/null +++ b/test/spec/modules/pilotxBidAdapter_spec.js @@ -0,0 +1,244 @@ +// import or require modules necessary for the test, e.g.: +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { spec } from '../../../modules/pilotxBidAdapter.js'; + +describe('pilotxAdapter', function () { + describe('isBidRequestValid', function () { + let banner; + beforeEach(function () { + banner = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + sizes: [[300, 250], [468, 60]], + bidId: '2de8c82e30665a', + params: { + placementId: '1' + } + }; + }); + + it('should return false if sizes is empty', function () { + banner.sizes = [] + expect(spec.isBidRequestValid(banner)).to.equal(false); + }); + it('should return true if all is valid/ is not empty', function () { + expect(spec.isBidRequestValid(banner)).to.equal(true); + }); + it('should return false if there is no placement id found', function () { + banner.params = {} + expect(spec.isBidRequestValid(banner)).to.equal(false); + }); + it('should return false if sizes is empty', function () { + banner.sizes = [] + expect(spec.isBidRequestValid(banner)).to.equal(false); + }); + it('should return false for no size and empty params', function() { + const emptySizes = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + bidId: '2de8c82e30665a', + params: { + placementId: '1', + sizes: [] + } + }; + expect(spec.isBidRequestValid(emptySizes)).to.equal(false); + }) + it('should return true for no size and valid size params', function() { + const emptySizes = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + bidId: '2de8c82e30665a', + params: { + placementId: '1', + sizes: [[300, 250], [468, 60]] + } + }; + expect(spec.isBidRequestValid(emptySizes)).to.equal(true); + }) + it('should return false for no size items', function() { + const emptySizes = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + bidId: '2de8c82e30665a', + params: { + placementId: '1' + } + }; + expect(spec.isBidRequestValid(emptySizes)).to.equal(false); + }) + }); + + describe('buildRequests', function () { + const mockRequest = { refererInfo: {} }; + const mockRequestGDPR = { + refererInfo: {}, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + gdprApplies: true + } + + } + const mockVideo1 = [{ + adUnitCode: 'video1', + auctionId: '01618029-7ae9-4e98-a73a-1ed0c817f414', + bidId: '2a59588c0114fa', + bidRequestsCount: 1, + bidder: 'pilotx', + bidderRequestId: '1f6b4ba2039726', + bidderRequestsCount: 1, + bidderWinsCount: 0, + crumbs: { pubcid: 'de5240ef-ff80-4b55-8837-26a11cfbf64c' }, + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mp4'], + playbackmethod: [2], + playerSize: [[640, 480]], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1 + } + }, + ortb2Imp: { + ext: { + data: { + pbadslot: 'video1' + } + } + }, + params: { placementId: '379' }, + sizes: [[640, 480]], + src: 'client', + transactionId: 'fec9f2ff-da13-4921-8437-8d679c2be7fe', + }]; + const mockVideo2 = [{ + adUnitCode: 'video1', + auctionId: '01618029-7ae9-4e98-a73a-1ed0c817f414', + bidId: '2a59588c0114fa', + bidRequestsCount: 1, + bidder: 'pilotx', + bidderRequestId: '1f6b4ba2039726', + bidderRequestsCount: 1, + bidderWinsCount: 0, + crumbs: { pubcid: 'de5240ef-ff80-4b55-8837-26a11cfbf64c' }, + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mp4'], + playbackmethod: [2], + playerSize: [[640, 480]], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1 + } + }, + ortb2Imp: { + ext: { + data: { + pbadslot: 'video1' + } + } + }, + params: { placementId: '379' }, + sizes: [640, 480], + src: 'client', + transactionId: 'fec9f2ff-da13-4921-8437-8d679c2be7fe', + }]; + it('should return correct response', function () { + const builtRequest = spec.buildRequests(mockVideo1, mockRequest) + let builtRequestData = builtRequest.data + let data = JSON.parse(builtRequestData) + expect(data['379'].bidId).to.equal(mockVideo1[0].bidId) + }); + it('should return correct response for only array of size', function () { + const builtRequest = spec.buildRequests(mockVideo2, mockRequest) + let builtRequestData = builtRequest.data + let data = JSON.parse(builtRequestData) + expect(data['379'].sizes[0][0]).to.equal(mockVideo2[0].sizes[0]) + expect(data['379'].sizes[0][1]).to.equal(mockVideo2[0].sizes[1]) + }); + it('should be valid and pass gdpr items correctly', function () { + const builtRequest = spec.buildRequests(mockVideo2, mockRequestGDPR) + let builtRequestData = builtRequest.data + let data = JSON.parse(builtRequestData) + expect(data['379'].gdprConsentString).to.equal(mockRequestGDPR.gdprConsent.consentString) + expect(data['379'].gdprConsentRequired).to.equal(mockRequestGDPR.gdprConsent.gdprApplies) + }); + }); + describe('interpretResponse', function () { + const bidRequest = {} + const serverResponse = { + cpm: 2.5, + creativeId: 'V9060', + currency: 'US', + height: 480, + mediaType: 'video', + netRevenue: false, + requestId: '273b39c74069cb', + ttl: 3000, + vastUrl: 'http://testadserver.com/ads?&k=60cd901ad8ab70c9cedf373cb17b93b8&pid=379&tid=91342717', + width: 640 + } + const serverResponseVideo = { + body: serverResponse + } + const serverResponse2 = { + cpm: 2.5, + creativeId: 'V9060', + currency: 'US', + height: 480, + mediaType: 'banner', + netRevenue: false, + requestId: '273b39c74069cb', + ttl: 3000, + vastUrl: 'http://testadserver.com/ads?&k=60cd901ad8ab70c9cedf373cb17b93b8&pid=379&tid=91342717', + width: 640 + } + const serverResponseBanner = { + body: serverResponse2 + } + it('should be valid from bidRequest for video', function () { + const bidResponses = spec.interpretResponse(serverResponseVideo, bidRequest) + expect(bidResponses[0].requestId).to.equal(serverResponse.requestId) + expect(bidResponses[0].cpm).to.equal(serverResponse.cpm) + expect(bidResponses[0].width).to.equal(serverResponse.width) + expect(bidResponses[0].height).to.equal(serverResponse.height) + expect(bidResponses[0].creativeId).to.equal(serverResponse.creativeId) + expect(bidResponses[0].currency).to.equal(serverResponse.currency) + expect(bidResponses[0].netRevenue).to.equal(serverResponse.netRevenue) + expect(bidResponses[0].ttl).to.equal(serverResponse.ttl) + expect(bidResponses[0].vastUrl).to.equal(serverResponse.vastUrl) + expect(bidResponses[0].mediaType).to.equal(serverResponse.mediaType) + expect(bidResponses[0].meta.mediaType).to.equal(serverResponse.mediaType) + }); + it('should be valid from bidRequest for banner', function () { + const bidResponses = spec.interpretResponse(serverResponseBanner, bidRequest) + expect(bidResponses[0].requestId).to.equal(serverResponse2.requestId) + expect(bidResponses[0].cpm).to.equal(serverResponse2.cpm) + expect(bidResponses[0].width).to.equal(serverResponse2.width) + expect(bidResponses[0].height).to.equal(serverResponse2.height) + expect(bidResponses[0].creativeId).to.equal(serverResponse2.creativeId) + expect(bidResponses[0].currency).to.equal(serverResponse2.currency) + expect(bidResponses[0].netRevenue).to.equal(serverResponse2.netRevenue) + expect(bidResponses[0].ttl).to.equal(serverResponse2.ttl) + expect(bidResponses[0].ad).to.equal(serverResponse2.ad) + expect(bidResponses[0].mediaType).to.equal(serverResponse2.mediaType) + expect(bidResponses[0].meta.mediaType).to.equal(serverResponse2.mediaType) + }); + }); + describe('setPlacementID', function () { + const multiplePlacementIds = ['380', '381'] + it('should be valid with an array of placement ids passed', function () { + const placementID = spec.setPlacementID(multiplePlacementIds) + expect(placementID).to.equal('380#381') + }); + it('should be valid with single placement ID passed', function () { + const placementID = spec.setPlacementID('381') + expect(placementID).to.equal('381') + }); + }); + // Add other `describe` or `it` blocks as necessary +}); diff --git a/test/spec/modules/pixfutureBidAdapter_spec.js b/test/spec/modules/pixfutureBidAdapter_spec.js new file mode 100644 index 00000000000..a236478c9b4 --- /dev/null +++ b/test/spec/modules/pixfutureBidAdapter_spec.js @@ -0,0 +1,255 @@ +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { spec } from 'modules/pixfutureBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; +import { auctionManager } from 'src/auctionManager.js'; +import { deepClone } from 'src/utils.js'; +import { config } from 'src/config.js'; + +describe('PixFutureAdapter', function () { + it('', function () { + const adapter = newBidder(spec); + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + // Test of isBidRequestValid method + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'pixfuture', + 'pageUrl': 'https://adinify.com/prebidjs/?pbjs_debug=true', + 'bidId': '236e806f760f0c', + 'auctionId': 'aa7f5d76-806b-4e0d-b795-cd6bd84ddc63', + 'transactionId': '0fdf67c0-7b48-4fef-9716-cc64d948e95d', + 'adUnitCode': '26335x300x250x14x_ADSLOT88', + 'sizes': [[300, 250], [300, 600]], + 'params': { + 'pix_id': '777' + } + }; + it('should return true when required params found (bid)', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found (bid.param=true)', function () { + delete bid.params; + bid.params = { + 'pix_id': '777' + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'pix_id': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + // Test of buildRequest method + + describe('Test of buildRequest method', function () { + let validBidRequests = [{ + 'labelAny': ['display'], + 'bidder': 'pixfuture', + 'params': { + 'pix_id': '777' + }, + 'userId': { + 'criteoId': 'P9iqSF9MSDVlZ2ZwdTVGanp5U2l0MWt1cnZya25TdEs4VlY4ZjNTeHQ4czlJUkZES0NFRXBZblJNcTNYYjU4MWxYS2VWalM5dnd5RUhRYm0lMkJuQUFNVm1iclRvSVJTYzBuNkcxZUtTa2duRyUyQnU4S3clM0Q', + 'id5id': { + 'uid': 'ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x', + 'ext': { + 'linkType': 2 + } + }, + 'pubcid': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61', + 'sharedid': { + 'id': '01EXPPGZ9C8NKG1MTXVHV98505', + 'third': '01EXPPGZ9C8NKG1MTXVHV98505' + } + }, + 'userIdAsEids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'P9iqSF9MSDVlZ2ZwdTVGanp5U2l0MWt1cnZya25TdEs4VlY4ZjNTeHQ4czlJUkZES0NFRXBZblJNcTNYYjU4MWxYS2VWalM5dnd5RUhRYm0lMkJuQUFNVm1iclRvSVJTYzBuNkcxZUtTa2duRyUyQnU4S3clM0Q', + 'atype': 1 + }] + }, { + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x', + 'atype': 1, + 'ext': { + 'linkType': 2 + } + }] + }, { + 'source': 'pubcid.org', + 'uids': [{ + 'id': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61', + 'atype': 1 + }] + }, { + 'source': 'sharedid.org', + 'uids': [{ + 'id': '01EXPPGZ9C8NKG1MTXVHV98505', + 'atype': 1, + 'ext': { + 'third': '01EXPPGZ9C8NKG1MTXVHV98505' + } + }] + }], + 'crumbs': { + 'pubcid': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + 'adUnitCode': '26335x300x250x14x_ADSLOT88', + 'transactionId': '09310832-cd12-478c-86dd-fbd819dff9d3', + 'sizes': [ + [300, 250] + ], + 'bidId': '279272f27dfb3e', + 'bidderRequestId': '10a0de227377a3', + 'auctionId': '4cd5684b-ae2a-4d1f-84be-5f1ee66d9ff3', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'pixfuture.com', + 'sid': '14', + 'hp': 1 + }] + } + }]; + + let bidderRequests = + { + 'bidderCode': 'pixfuture', + 'auctionId': '4cd5684b-ae2a-4d1f-84be-5f1ee66d9ff3', + 'bidderRequestId': '10a0de227377a3', + 'bids': [{ + 'labelAny': ['display'], + 'bidder': 'pixfuture', + 'params': { + 'pix_id': '777' + }, + 'userId': { + 'criteoId': 'P9iqSF9MSDVlZ2ZwdTVGanp5U2l0MWt1cnZya25TdEs4VlY4ZjNTeHQ4czlJUkZES0NFRXBZblJNcTNYYjU4MWxYS2VWalM5dnd5RUhRYm0lMkJuQUFNVm1iclRvSVJTYzBuNkcxZUtTa2duRyUyQnU4S3clM0Q', + 'id5id': { + 'uid': 'ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x', + 'ext': { + 'linkType': 2 + } + }, + 'pubcid': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61', + 'sharedid': { + 'id': '01EXPPGZ9C8NKG1MTXVHV98505', + 'third': '01EXPPGZ9C8NKG1MTXVHV98505' + } + }, + 'userIdAsEids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'P9iqSF9MSDVlZ2ZwdTVGanp5U2l0MWt1cnZya25TdEs4VlY4ZjNTeHQ4czlJUkZES0NFRXBZblJNcTNYYjU4MWxYS2VWalM5dnd5RUhRYm0lMkJuQUFNVm1iclRvSVJTYzBuNkcxZUtTa2duRyUyQnU4S3clM0Q', + 'atype': 1 + }] + }, { + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x', + 'atype': 1, + 'ext': { + 'linkType': 2 + } + }] + }, { + 'source': 'pubcid.org', + 'uids': [{ + 'id': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61', + 'atype': 1 + }] + }, { + 'source': 'sharedid.org', + 'uids': [{ + 'id': '01EXPPGZ9C8NKG1MTXVHV98505', + 'atype': 1, + 'ext': { + 'third': '01EXPPGZ9C8NKG1MTXVHV98505' + } + }] + }], + 'crumbs': { + 'pubcid': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + 'adUnitCode': '26335x300x250x14x_ADSLOT88', + 'transactionId': '09310832-cd12-478c-86dd-fbd819dff9d3', + 'sizes': [ + [300, 250] + ], + 'bidId': '279272f27dfb3e', + 'bidderRequestId': '10a0de227377a3', + 'auctionId': '4cd5684b-ae2a-4d1f-84be-5f1ee66d9ff3', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'pixfuture.com', + 'sid': '14', + 'hp': 1 + }] + } + }], + 'auctionStart': 1620934247115, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'https://adinify.com/prebidjs/?pbjs_debug=true', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': ['https://adinify.com/prebidjs/?pbjs_debug=true'], + 'canonicalUrl': null + }, + 'start': 1620934247117 + }; + + // let bidderRequest = Object.assign({}, bidderRequests); + const request = spec.buildRequests(validBidRequests, bidderRequests); + // console.log(JSON.stringify(request)); + let bidRequest = Object.assign({}, request[0]); + + expect(bidRequest.data).to.exist; + expect(bidRequest.data.sizes).to.deep.equal([[300, 250]]); + expect(bidRequest.data.params).to.deep.equal({'pix_id': '777'}); + expect(bidRequest.data.adUnitCode).to.deep.equal('26335x300x250x14x_ADSLOT88'); + }); + }); + // Add other `describe` or `it` blocks as necessary +}); diff --git a/test/spec/modules/piximediaBidAdapter_spec.js b/test/spec/modules/piximediaBidAdapter_spec.js deleted file mode 100644 index 88a6433b71d..00000000000 --- a/test/spec/modules/piximediaBidAdapter_spec.js +++ /dev/null @@ -1,103 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/piximediaBidAdapter.js'; - -describe('piximediaAdapterTest', function() { - describe('bidRequestValidity', function() { - it('bidRequest with site ID and placement ID param', function() { - expect(spec.isBidRequestValid({ - bidder: 'piximedia', - params: { - 'siteId': 'PIXIMEDIA_PREBID10', - 'placementId': 'RG' - }, - })).to.equal(true); - }); - - it('bidRequest with no required params', function() { - expect(spec.isBidRequestValid({ - bidder: 'piximedia', - params: { - }, - })).to.equal(false); - }); - }); - - describe('bidRequest', function() { - const bidRequests = [{ - 'bidder': 'piximedia', - 'params': { - 'siteId': 'PIXIMEDIA_PREBID10', - 'placementId': 'RG' - }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'sizes': [300, 250], - 'bidId': '51ef8751f9aead', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757' - }]; - - it('bidRequest HTTP method', function() { - const requests = spec.buildRequests(bidRequests); - requests.forEach(function(requestItem) { - expect(requestItem.method).to.equal('GET'); - }); - }); - - it('bidRequest data', function() { - const requests = spec.buildRequests(bidRequests); - expect(typeof requests[0].data.timestamp).to.equal('number'); - expect(requests[0].data.pbsizes).to.equal('["300x250"]'); - expect(requests[0].data.pver).to.equal('1.0'); - expect(requests[0].data.pbparams).to.equal(JSON.stringify(bidRequests[0].params)); - expect(requests[0].data.pbwidth).to.equal('300'); - expect(requests[0].data.pbheight).to.equal('250'); - expect(requests[0].data.pbbidid).to.equal('51ef8751f9aead'); - }); - }); - - describe('interpretResponse', function() { - const bidRequest = { - 'method': 'GET', - 'url': 'https://ad.piximedia.com/', - 'data': { - 'ver': 2, - 'hb': 1, - 'output': 'js', - 'pub': 267, - 'zone': 62546, - 'width': '300', - 'height': '250', - 'callback': 'json', - 'callback_uid': '51ef8751f9aead', - 'url': 'https://example.com', - 'cb': '', - } - }; - - const bidResponse = { - body: { - 'bidId': '51ef8751f9aead', - 'cpm': 4.2, - 'width': '300', - 'height': '250', - 'creative_id': '1234', - 'currency': 'EUR', - 'adm': '
', - }, - headers: {} - }; - - it('result is correct', function() { - const result = spec.interpretResponse(bidResponse, bidRequest); - expect(result[0].requestId).to.equal('51ef8751f9aead'); - expect(result[0].cpm).to.equal(4.2); - expect(result[0].width).to.equal('300'); - expect(result[0].height).to.equal('250'); - expect(result[0].creativeId).to.equal('1234'); - expect(result[0].currency).to.equal('EUR'); - expect(result[0].ttl).to.equal(300); - expect(result[0].ad).to.equal('
'); - }); - }); -}); diff --git a/test/spec/modules/platformioBidAdapter_spec.js b/test/spec/modules/platformioBidAdapter_spec.js deleted file mode 100644 index ee753be17a7..00000000000 --- a/test/spec/modules/platformioBidAdapter_spec.js +++ /dev/null @@ -1,348 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/platformioBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; - -describe('Platform.io Adapter Tests', function () { - const slotConfigs = [{ - placementCode: '/DfpAccount1/slot1', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bidId: 'bid12345', - mediaType: 'banner', - params: { - pubId: '29521', - siteId: '26047', - placementId: '123', - bidFloor: '0.001', - ifa: 'IFA', - latitude: '40.712775', - longitude: '-74.005973' - } - }, { - placementCode: '/DfpAccount2/slot2', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'bid23456', - mediaType: 'banner', - params: { - pubId: '29521', - siteId: '26047', - placementId: '1234', - bidFloor: '0.000001', - } - }]; - const nativeSlotConfig = [{ - placementCode: '/DfpAccount1/slot3', - bidId: 'bid12345', - mediaType: 'native', - nativeParams: { - title: { required: true, len: 200 }, - body: {}, - image: { wmin: 100 }, - sponsoredBy: { }, - icon: { } - }, - params: { - pubId: '29521', - placementId: '123', - siteId: '26047' - } - }]; - const videoSlotConfig = [{ - placementCode: '/DfpAccount1/slot4', - mediaTypes: { - video: { - playerSize: [[640, 480]] - } - }, - bidId: 'bid12345678', - mediaType: 'video', - video: { - skippable: true - }, - params: { - pubId: '29521', - placementId: '1234567', - siteId: '26047', - } - }]; - const appSlotConfig = [{ - placementCode: '/DfpAccount1/slot5', - bidId: 'bid12345', - params: { - pubId: '29521', - placementId: '1234', - app: { - id: '1111', - name: 'app name', - bundle: 'io.platform.apps', - storeUrl: 'https://platform.io/apps', - domain: 'platform.io' - } - } - }]; - - it('Verify build request', function () { - const request = spec.buildRequests(slotConfigs); - expect(request.url).to.equal('https://piohbdisp.hb.adx1.com/'); - expect(request.method).to.equal('POST'); - const ortbRequest = JSON.parse(request.data); - // site object - expect(ortbRequest.site).to.not.equal(null); - expect(ortbRequest.site.publisher).to.not.equal(null); - expect(ortbRequest.site.publisher.id).to.equal('29521'); - expect(ortbRequest.site.ref).to.equal(window.top.document.referrer); - expect(ortbRequest.site.page).to.equal(window.location.href); - expect(ortbRequest.imp).to.have.lengthOf(2); - // device object - expect(ortbRequest.device).to.not.equal(null); - expect(ortbRequest.device.ua).to.equal(navigator.userAgent); - expect(ortbRequest.device.ifa).to.equal('IFA'); - expect(ortbRequest.device.geo.lat).to.equal('40.712775'); - expect(ortbRequest.device.geo.lon).to.equal('-74.005973'); - // slot 1 - expect(ortbRequest.imp[0].tagid).to.equal('123'); - expect(ortbRequest.imp[0].banner).to.not.equal(null); - expect(ortbRequest.imp[0].banner.w).to.equal(300); - expect(ortbRequest.imp[0].banner.h).to.equal(250); - expect(ortbRequest.imp[0].bidfloor).to.equal('0.001'); - // slot 2 - expect(ortbRequest.imp[1].tagid).to.equal('1234'); - expect(ortbRequest.imp[1].banner).to.not.equal(null); - expect(ortbRequest.imp[1].banner.w).to.equal(728); - expect(ortbRequest.imp[1].banner.h).to.equal(90); - expect(ortbRequest.imp[1].bidfloor).to.equal('0.000001'); - }); - - it('Verify parse response', function () { - const request = spec.buildRequests(slotConfigs); - const ortbRequest = JSON.parse(request.data); - const ortbResponse = { - seatbid: [{ - bid: [{ - impid: ortbRequest.imp[0].id, - price: 1.25, - adm: 'This is an Ad', - w: 300, - h: 250 - }] - }], - cur: 'USD' - }; - const bids = spec.interpretResponse({ body: ortbResponse }, request); - expect(bids).to.have.lengthOf(1); - // verify first bid - const bid = bids[0]; - expect(bid.cpm).to.equal(1.25); - expect(bid.ad).to.equal('This is an Ad'); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.adId).to.equal('bid12345'); - expect(bid.creativeId).to.equal('bid12345'); - expect(bid.netRevenue).to.equal(true); - expect(bid.currency).to.equal('USD'); - expect(bid.ttl).to.equal(360); - }); - - it('Verify full passback', function () { - const request = spec.buildRequests(slotConfigs); - const bids = spec.interpretResponse({ body: null }, request) - expect(bids).to.have.lengthOf(0); - }); - - it('Verify Native request', function () { - const request = spec.buildRequests(nativeSlotConfig); - expect(request.url).to.equal('https://piohbdisp.hb.adx1.com/'); - expect(request.method).to.equal('POST'); - const ortbRequest = JSON.parse(request.data); - // native impression - expect(ortbRequest.imp[0].tagid).to.equal('123'); - const nativePart = ortbRequest.imp[0]['native']; - expect(nativePart).to.not.equal(null); - expect(nativePart.ver).to.equal('1.1'); - expect(nativePart.request).to.not.equal(null); - // native request assets - const nativeRequest = JSON.parse(ortbRequest.imp[0]['native'].request); - expect(nativeRequest).to.not.equal(null); - expect(nativeRequest.assets).to.have.lengthOf(5); - expect(nativeRequest.assets[0].id).to.equal(1); - expect(nativeRequest.assets[1].id).to.equal(2); - expect(nativeRequest.assets[2].id).to.equal(3); - expect(nativeRequest.assets[3].id).to.equal(4); - expect(nativeRequest.assets[4].id).to.equal(5); - expect(nativeRequest.assets[0].required).to.equal(1); - expect(nativeRequest.assets[0].title).to.not.equal(null); - expect(nativeRequest.assets[0].title.len).to.equal(200); - expect(nativeRequest.assets[1].title).to.be.undefined; - expect(nativeRequest.assets[1].data).to.not.equal(null); - expect(nativeRequest.assets[1].data.type).to.equal(2); - expect(nativeRequest.assets[1].data.len).to.equal(200); - expect(nativeRequest.assets[2].required).to.equal(0); - expect(nativeRequest.assets[3].img).to.not.equal(null); - expect(nativeRequest.assets[3].img.wmin).to.equal(50); - expect(nativeRequest.assets[3].img.hmin).to.equal(50); - expect(nativeRequest.assets[3].img.type).to.equal(1); - expect(nativeRequest.assets[4].img).to.not.equal(null); - expect(nativeRequest.assets[4].img.wmin).to.equal(100); - expect(nativeRequest.assets[4].img.hmin).to.equal(150); - expect(nativeRequest.assets[4].img.type).to.equal(3); - }); - - it('Verify Native response', function () { - const request = spec.buildRequests(nativeSlotConfig); - expect(request.url).to.equal('https://piohbdisp.hb.adx1.com/'); - expect(request.method).to.equal('POST'); - const ortbRequest = JSON.parse(request.data); - const nativeResponse = { - 'native': { - assets: [ - { id: 1, title: { text: 'Ad Title' } }, - { id: 2, data: { value: 'Test description' } }, - { id: 3, data: { value: 'Brand' } }, - { id: 4, img: { url: 'https://adx1public.s3.amazonaws.com/creatives_icon.png', w: 100, h: 100 } }, - { id: 5, img: { url: 'https://adx1public.s3.amazonaws.com/creatives_image.png', w: 300, h: 300 } } - ], - link: { url: 'https://brand.com/' } - } - }; - const ortbResponse = { - seatbid: [{ - bid: [{ - impid: ortbRequest.imp[0].id, - price: 1.25, - nurl: 'https://rtb.adx1.com/log', - adm: JSON.stringify(nativeResponse) - }] - }], - cur: 'USD', - }; - const bids = spec.interpretResponse({ body: ortbResponse }, request); - // verify bid - const bid = bids[0]; - expect(bid.cpm).to.equal(1.25); - expect(bid.adId).to.equal('bid12345'); - expect(bid.ad).to.be.undefined; - expect(bid.mediaType).to.equal('native'); - const nativeBid = bid['native']; - expect(nativeBid).to.not.equal(null); - expect(nativeBid.title).to.equal('Ad Title'); - expect(nativeBid.sponsoredBy).to.equal('Brand'); - expect(nativeBid.icon.url).to.equal('https://adx1public.s3.amazonaws.com/creatives_icon.png'); - expect(nativeBid.image.url).to.equal('https://adx1public.s3.amazonaws.com/creatives_image.png'); - expect(nativeBid.image.width).to.equal(300); - expect(nativeBid.image.height).to.equal(300); - expect(nativeBid.icon.width).to.equal(100); - expect(nativeBid.icon.height).to.equal(100); - expect(nativeBid.clickUrl).to.equal(encodeURIComponent('https://brand.com/')); - expect(nativeBid.impressionTrackers).to.have.lengthOf(1); - expect(nativeBid.impressionTrackers[0]).to.equal('https://rtb.adx1.com/log'); - }); - - it('Verify Video request', function () { - const request = spec.buildRequests(videoSlotConfig); - expect(request.url).to.equal('https://piohbdisp.hb.adx1.com/'); - expect(request.method).to.equal('POST'); - const videoRequest = JSON.parse(request.data); - // site object - expect(videoRequest.site).to.not.equal(null); - expect(videoRequest.site.publisher.id).to.equal('29521'); - expect(videoRequest.site.ref).to.equal(window.top.document.referrer); - expect(videoRequest.site.page).to.equal(window.location.href); - // device object - expect(videoRequest.device).to.not.equal(null); - expect(videoRequest.device.ua).to.equal(navigator.userAgent); - // slot 1 - expect(videoRequest.imp[0].tagid).to.equal('1234567'); - expect(videoRequest.imp[0].video).to.not.equal(null); - expect(videoRequest.imp[0].video.w).to.equal(640); - expect(videoRequest.imp[0].video.h).to.equal(480); - expect(videoRequest.imp[0].banner).to.equal(null); - expect(videoRequest.imp[0].native).to.equal(null); - }); - - it('Verify parse video response', function () { - const request = spec.buildRequests(videoSlotConfig); - const videoRequest = JSON.parse(request.data); - const videoResponse = { - seatbid: [{ - bid: [{ - impid: videoRequest.imp[0].id, - price: 1.90, - adm: 'https://vid.example.com/9876', - crid: '510511_754567308' - }] - }], - cur: 'USD' - }; - const bids = spec.interpretResponse({ body: videoResponse }, request); - expect(bids).to.have.lengthOf(1); - // verify first bid - const bid = bids[0]; - expect(bid.cpm).to.equal(1.90); - expect(bid.vastUrl).to.equal('https://vid.example.com/9876'); - expect(bid.crid).to.equal('510511_754567308'); - expect(bid.width).to.equal(640); - expect(bid.height).to.equal(480); - expect(bid.adId).to.equal('bid12345678'); - expect(bid.netRevenue).to.equal(true); - expect(bid.currency).to.equal('USD'); - expect(bid.ttl).to.equal(360); - }); - - it('Verifies bidder code', function () { - expect(spec.code).to.equal('platformio'); - }); - - it('Verifies supported media types', function () { - expect(spec.supportedMediaTypes).to.have.lengthOf(3); - expect(spec.supportedMediaTypes[0]).to.equal('banner'); - expect(spec.supportedMediaTypes[1]).to.equal('native'); - expect(spec.supportedMediaTypes[2]).to.equal('video'); - }); - - it('Verifies if bid request valid', function () { - expect(spec.isBidRequestValid(slotConfigs[0])).to.equal(true); - expect(spec.isBidRequestValid(slotConfigs[1])).to.equal(true); - expect(spec.isBidRequestValid(nativeSlotConfig[0])).to.equal(true); - expect(spec.isBidRequestValid(videoSlotConfig[0])).to.equal(true); - }); - - it('Verify app requests', function () { - const request = spec.buildRequests(appSlotConfig); - const ortbRequest = JSON.parse(request.data); - expect(ortbRequest.site).to.equal(null); - expect(ortbRequest.app).to.not.be.null; - expect(ortbRequest.app.publisher).to.not.equal(null); - expect(ortbRequest.app.publisher.id).to.equal('29521'); - expect(ortbRequest.app.id).to.equal('1111'); - expect(ortbRequest.app.name).to.equal('app name'); - expect(ortbRequest.app.bundle).to.equal('io.platform.apps'); - expect(ortbRequest.app.storeurl).to.equal('https://platform.io/apps'); - expect(ortbRequest.app.domain).to.equal('platform.io'); - }); - - it('Verify GDPR', function () { - const bidderRequest = { - gdprConsent: { - gdprApplies: true, - consentString: 'serialized_gpdr_data' - } - }; - const request = spec.buildRequests(slotConfigs, bidderRequest); - expect(request.url).to.equal('https://piohbdisp.hb.adx1.com/'); - expect(request.method).to.equal('POST'); - const ortbRequest = JSON.parse(request.data); - expect(ortbRequest.user).to.not.equal(null); - expect(ortbRequest.user.ext).to.not.equal(null); - expect(ortbRequest.user.ext.consent).to.equal('serialized_gpdr_data'); - expect(ortbRequest.regs).to.not.equal(null); - expect(ortbRequest.regs.ext).to.not.equal(null); - expect(ortbRequest.regs.ext.gdpr).to.equal(1); - }); -}); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index a6afed8ba3e..0d62f458969 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -8,6 +8,15 @@ import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import { server } from 'test/mocks/xhr.js'; import { createEidsArray } from 'modules/userId/eids.js'; +import {deepAccess, deepClone} from 'src/utils.js'; +import 'modules/appnexusBidAdapter.js' // appnexus alias test +import 'modules/rubiconBidAdapter.js' // rubicon alias test +import 'src/prebid.js' // $$PREBID_GLOBAL$$.aliasBidder test +import 'modules/currency.js' // adServerCurrency test +import {hook} from '../../../src/hook.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; +import {auctionManager} from '../../../src/auctionManager.js'; +import {stubAuctionIndex} from '../../helpers/indexStub.js'; let CONFIG = { accountId: '1', @@ -124,7 +133,16 @@ const OUTSTREAM_VIDEO_REQUEST = { 'video': { playerSize: [[640, 480]], context: 'outstream', - mimes: ['video/mp4'] + mimes: ['video/mp4'], + renderer: { + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: function (bid) { + ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid.adResponse, + }); + } + } }, banner: { sizes: [[300, 250]] } }, @@ -143,7 +161,8 @@ const OUTSTREAM_VIDEO_REQUEST = { video: { playerSize: [640, 480], context: 'outstream', - mimes: ['video/mp4'] + mimes: ['video/mp4'], + skip: 1 } }, bids: [ @@ -157,16 +176,7 @@ const OUTSTREAM_VIDEO_REQUEST = { } } } - ], - renderer: { - url: 'http://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', - render: function (bid) { - ANOutstreamVideo.renderAd({ - targetId: bid.adUnitCode, - adResponse: bid.adResponse, - }); - } - } + ] } ], }; @@ -271,9 +281,11 @@ const RESPONSE_OPENRTB = { 'type': 'banner', 'event': { 'win': 'http://wurl.org?id=333' + }, + 'meta': { + 'dchain': { 'ver': '1.0', 'complete': 0, 'nodes': [ { 'asi': 'magnite.com', 'bsid': '123456789', } ] } } }, - 'dchain': { 'ver': '1.0', 'complete': 0, 'nodes': [ { 'asi': 'magnite.com', 'bsid': '123456789', } ] }, 'bidder': { 'appnexus': { 'brand_id': 1, @@ -444,6 +456,16 @@ describe('S2S Adapter', function () { addBidResponse = sinon.spy(), done = sinon.spy(); + function prepRequest(req) { + req.ad_units.forEach((adUnit) => { delete adUnit.nativeParams }); + decorateAdUnitsWithNativeParams(req.ad_units); + } + + before(() => { + hook.ready(); + prepRequest(REQUEST); + }); + beforeEach(function () { config.resetConfig(); adapter = new Adapter(); @@ -504,14 +526,79 @@ describe('S2S Adapter', function () { resetSyncedStatus(); }); - it('should not add outstrean without renderer', function () { + it('should set id to auction ID and source.tid to tid', function () { + config.setConfig({ s2sConfig: CONFIG }); + + adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.id).to.equal('173afb6d132ba3'); + expect(requestBid.source).to.be.an('object'); + expect(requestBid.source.tid).to.equal('437fbbf5-33f5-487a-8e16-a7112903cfe5'); + }); + + it('should block request if config did not define p1Consent URL in endpoint object config', function() { + let badConfig = utils.deepClone(CONFIG); + badConfig.endpoint = { noP1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }; + config.setConfig({ s2sConfig: badConfig }); + + let badCfgRequest = utils.deepClone(REQUEST); + badCfgRequest.s2sConfig = badConfig; + + adapter.callBids(badCfgRequest, BID_REQUESTS, addBidResponse, done, ajax); + + expect(server.requests.length).to.equal(0); + }); + + it('should block request if config did not define noP1Consent URL in endpoint object config', function() { + let badConfig = utils.deepClone(CONFIG); + badConfig.endpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }; + config.setConfig({ s2sConfig: badConfig }); + + let badCfgRequest = utils.deepClone(REQUEST); + badCfgRequest.s2sConfig = badConfig; + + let badBidderRequest = utils.deepClone(BID_REQUESTS); + badBidderRequest[0].gdprConsent = { + consentString: 'abc123', + addtlConsent: 'superduperconsent', + gdprApplies: true, + apiVersion: 2, + vendorData: { + purpose: { + consents: { + 1: false + } + } + } + }; + + adapter.callBids(badCfgRequest, badBidderRequest, addBidResponse, done, ajax); + + expect(server.requests.length).to.equal(0); + }); + + it('should block request if config did not define any URLs in endpoint object config', function() { + let badConfig = utils.deepClone(CONFIG); + badConfig.endpoint = {}; + config.setConfig({ s2sConfig: badConfig }); + + let badCfgRequest = utils.deepClone(REQUEST); + badCfgRequest.s2sConfig = badConfig; + + adapter.callBids(badCfgRequest, BID_REQUESTS, addBidResponse, done, ajax); + + expect(server.requests.length).to.equal(0); + }); + + it('should add outstream bc renderer exists on mediatype', function () { config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.imp[0].banner).to.exist; - expect(requestBid.imp[0].video).to.not.exist; + expect(requestBid.imp[0].video).to.exist; }); it('should default video placement if not defined and instream', function () { @@ -530,6 +617,26 @@ describe('S2S Adapter', function () { expect(requestBid.imp[0].video.placement).to.equal(1); }); + it('converts video mediaType properties into openRTB format', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + + config.setConfig({ s2sConfig: ortb2Config }); + + let videoBid = utils.deepClone(VIDEO_REQUEST); + videoBid.ad_units[0].mediaTypes.video.context = 'instream'; + adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.imp[0].banner).to.not.exist; + expect(requestBid.imp[0].video).to.exist; + expect(requestBid.imp[0].video.placement).to.equal(1); + expect(requestBid.imp[0].video.w).to.equal(640); + expect(requestBid.imp[0].video.h).to.equal(480); + expect(requestBid.imp[0].video.playerSize).to.be.undefined; + expect(requestBid.imp[0].video.context).to.be.undefined; + }); + it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); @@ -1004,6 +1111,19 @@ describe('S2S Adapter', function () { }); }); + it('should not include ext.aspectratios if adunit\'s aspect_ratios do not define radio_width and ratio_height', () => { + const req = deepClone(REQUEST); + req.ad_units[0].mediaTypes.native.icon.aspect_ratios[0] = {'min_width': 1, 'min_height': 2}; + prepRequest(req); + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const nativeReq = JSON.parse(JSON.parse(server.requests[0].requestBody).imp[0].native.request); + const icons = nativeReq.assets.map((a) => a.img).filter((img) => img && img.type === 1); + expect(icons).to.have.length(1); + expect(icons[0].hmin).to.equal(2); + expect(icons[0].wmin).to.equal(1); + expect(deepAccess(icons[0], 'ext.aspectratios')).to.be.undefined; + }) + it('adds site if app is not present', function () { const _config = { s2sConfig: CONFIG, @@ -1123,6 +1243,10 @@ describe('S2S Adapter', function () { targeting: { includebidderkeys: false, includewinners: true + }, + channel: { + name: 'pbjs', + version: 'v$prebid.version$' } } }); @@ -1157,6 +1281,10 @@ describe('S2S Adapter', function () { targeting: { includebidderkeys: false, includewinners: true + }, + channel: { + name: 'pbjs', + version: 'v$prebid.version$' } } }); @@ -1610,6 +1738,34 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.ext.prebid.multibid).to.deep.equal(expected); }); + it('sets and passes pbjs version in request if channel does not exist in s2sConfig', () => { + const s2sBidRequest = utils.deepClone(REQUEST); + const bidRequests = utils.deepClone(BID_REQUESTS); + + adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); + + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: 'v$prebid.version$'}); + }); + + it('extPrebid is now mergedDeep -> should include default channel as well', () => { + const s2sBidRequest = utils.deepClone(REQUEST); + const bidRequests = utils.deepClone(BID_REQUESTS); + + utils.deepSetValue(s2sBidRequest, 's2sConfig.extPrebid.channel', {test: 1}); + + adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); + + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + // extPrebid is now deep merged with + expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({ + name: 'pbjs', + test: 1, + version: 'v$prebid.version$' + }); + }); + it('passes first party data in request', () => { const s2sBidRequest = utils.deepClone(REQUEST); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -1638,6 +1794,8 @@ describe('S2S Adapter', function () { interests: ['cars'] } }; + const bcat = ['IAB25', 'IAB7-39']; + const badv = ['blockedAdv-1.com', 'blockedAdv-2.com']; const allowedBidders = [ 'rubicon', 'appnexus' ]; const expected = allowedBidders.map(bidder => ({ @@ -1662,19 +1820,23 @@ describe('S2S Adapter', function () { interests: ['cars'] } } - } + }, + bcat: ['IAB25', 'IAB7-39'], + badv: ['blockedAdv-1.com', 'blockedAdv-2.com'] } } })); const commonContextExpected = utils.mergeDeep({'page': 'http://mytestpage.com', 'publisher': {'id': '1'}}, commonContext); - config.setConfig({ fpd: { context: commonContext, user: commonUser } }); - config.setBidderConfig({ bidders: allowedBidders, config: { fpd: { context, user } } }); + config.setConfig({ fpd: { context: commonContext, user: commonUser, badv, bcat } }); + config.setBidderConfig({ bidders: allowedBidders, config: { fpd: { context, user, bcat, badv } } }); adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); expect(parsedRequestBody.site).to.deep.equal(commonContextExpected); expect(parsedRequestBody.user).to.deep.equal(commonUser); + expect(parsedRequestBody.badv).to.deep.equal(badv); + expect(parsedRequestBody.bcat).to.deep.equal(bcat); }); describe('pbAdSlot config', function () { @@ -2261,11 +2423,8 @@ describe('S2S Adapter', function () { }); it('handles OpenRTB native responses', function () { - sinon.stub(utils, 'getBidRequest').returns({ - adUnitCode: 'div-gpt-ad-1460505748561-0', - bidder: 'appnexus', - bidId: '123' - }); + const stub = sinon.stub(auctionManager, 'index'); + stub.get(() => stubAuctionIndex({adUnits: REQUEST.ad_units})); const s2sConfig = Object.assign({}, CONFIG, { endpoint: { p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' @@ -2288,7 +2447,58 @@ describe('S2S Adapter', function () { expect(response).to.have.property('requestId', '123'); expect(response).to.have.property('cpm', 10); - utils.getBidRequest.restore(); + stub.restore(); + }); + + it('does not (by default) allow bids that were not requested', function () { + config.setConfig({ s2sConfig: CONFIG }); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const response = deepClone(RESPONSE_OPENRTB); + response.seatbid[0].seat = 'unknown'; + server.requests[0].respond(200, {}, JSON.stringify(response)); + + expect(addBidResponse.called).to.be.false; + }); + + it('allows unrequested bids if config.allowUnknownBidderCodes', function () { + const cfg = {...CONFIG, allowUnknownBidderCodes: true}; + config.setConfig({s2sConfig: cfg}); + adapter.callBids({...REQUEST, s2sConfig: cfg}, BID_REQUESTS, addBidResponse, done, ajax); + const response = deepClone(RESPONSE_OPENRTB); + response.seatbid[0].seat = 'unknown'; + server.requests[0].respond(200, {}, JSON.stringify(response)); + + expect(addBidResponse.calledWith(sinon.match.any, sinon.match({bidderCode: 'unknown'}))).to.be.true; + }); + + describe('on sync requested with no cookie', () => { + let cfg, req, csRes; + + beforeEach(() => { + cfg = utils.deepClone(CONFIG); + req = utils.deepClone(REQUEST); + cfg.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; + req.s2sConfig = cfg; + config.setConfig({ s2sConfig: cfg }); + csRes = utils.deepClone(RESPONSE_NO_COOKIE); + }); + + afterEach(() => { + resetSyncedStatus(); + }) + + Object.entries({ + iframe: () => utils.insertUserSyncIframe, + image: () => utils.triggerPixel, + }).forEach(([type, syncer]) => { + it(`passes timeout to ${type} syncs`, () => { + cfg.syncTimeout = 123; + csRes.bidder_status[0].usersync.type = type; + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(csRes)); + expect(syncer().args[0]).to.include.members([123]); + }); + }); }); }); @@ -2473,6 +2683,27 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('timeout', 750); }); + it('should configure the s2sConfig object with appnexuspsp vendor defaults unless specified by user', function () { + const options = { + accountId: '123', + bidders: ['appnexus'], + defaultVendor: 'appnexuspsp', + timeout: 750 + }; + + config.setConfig({ s2sConfig: options }); + sinon.assert.notCalled(logErrorSpy); + + let vendorConfig = config.getConfig('s2sConfig'); + expect(vendorConfig).to.have.property('accountId', '123'); + expect(vendorConfig).to.have.property('adapter', 'prebidServer'); + expect(vendorConfig.bidders).to.deep.equal(['appnexus']); + expect(vendorConfig.enabled).to.be.true; + expect(vendorConfig.endpoint).to.deep.equal({p1Consent: 'https://ib.adnxs.com/openrtb2/prebid', noP1Consent: 'https://ib.adnxs-simple.com/openrtb2/prebid'}); + expect(vendorConfig.syncEndpoint).to.be.undefined; + expect(vendorConfig).to.have.property('timeout', 750); + }); + it('should configure the s2sConfig object with rubicon vendor defaults unless specified by user', function () { const options = { accountId: 'abc', @@ -2678,5 +2909,16 @@ describe('S2S Adapter', function () { expect(requestBid.coopSync).to.be.undefined; }); + + it('adds debug flag', function () { + config.setConfig({debug: true}); + + let bidRequest = utils.deepClone(BID_REQUESTS); + + adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.ext.prebid.debug).is.equal(true); + }); }); }); diff --git a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js index 2505af0c2a7..e504eced868 100644 --- a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js +++ b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js @@ -108,6 +108,7 @@ describe('Prebid Manager Analytics Adapter', function () { describe('build utm tag data', function () { let getDataFromLocalStorageStub; + this.timeout(4000) beforeEach(function () { getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); getDataFromLocalStorageStub.withArgs('pm_utm_source').returns('utm_source'); @@ -115,7 +116,6 @@ describe('Prebid Manager Analytics Adapter', function () { getDataFromLocalStorageStub.withArgs('pm_utm_campaign').returns('utm_camp'); getDataFromLocalStorageStub.withArgs('pm_utm_term').returns(''); getDataFromLocalStorageStub.withArgs('pm_utm_content').returns(''); - getDataFromLocalStorageStub.withArgs('pm_utm_source').returns('utm_source'); }); afterEach(function () { getDataFromLocalStorageStub.restore(); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 548d2789d3e..076b0f96078 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -15,6 +15,11 @@ import { allowedFields } from 'modules/priceFloors.js'; import events from 'src/events.js'; +import * as mockGpt from '../integration/faker/googletag.js'; +import 'src/prebid.js'; +import {createBid} from '../../../src/bidfactory.js'; +import {auctionManager} from '../../../src/auctionManager.js'; +import {stubAuctionIndex} from '../../helpers/indexStub.js'; describe('the price floors module', function () { let logErrorSpy; @@ -108,6 +113,7 @@ describe('the price floors module', function () { bidder: 'rubicon', adUnitCode: 'test_div_1', auctionId: '1234-56-789', + transactionId: 'tr_test_div_1' }; function getAdUnitMock(code = 'adUnit-code') { @@ -398,6 +404,90 @@ describe('the price floors module', function () { matchingFloor: 5.0 }); }); + describe('with gpt enabled', function () { + let gptFloorData; + let indexStub, adUnits; + beforeEach(function () { + gptFloorData = { + currency: 'USD', + schema: { + fields: ['gptSlot'] + }, + values: { + '/12345/sports/soccer': 1.1, + '/12345/sports/basketball': 2.2, + '/12345/news/politics': 3.3, + '/12345/news/weather': 4.4, + '*': 5.5, + }, + default: 0.5 + }; + // reset it so no lingering stuff from other test specs + mockGpt.reset(); + mockGpt.makeSlot({ + code: '/12345/sports/soccer', + divId: 'test_div_1' + }); + mockGpt.makeSlot({ + code: '/12345/sports/basketball', + divId: 'test_div_2' + }); + indexStub = sinon.stub(auctionManager, 'index'); + indexStub.get(() => stubAuctionIndex({adUnits})) + }); + afterEach(function () { + // reset it so no lingering stuff from other test specs + mockGpt.reset(); + indexStub.restore(); + }); + it('picks the right rule when looking for gptSlot', function () { + expect(getFirstMatchingFloor(gptFloorData, basicBidRequest)).to.deep.equal({ + floorMin: 0, + floorRuleValue: 1.1, + matchingFloor: 1.1, + matchingData: '/12345/sports/soccer', + matchingRule: '/12345/sports/soccer' + }); + + let newBidRequest = { ...basicBidRequest, adUnitCode: 'test_div_2' } + expect(getFirstMatchingFloor(gptFloorData, newBidRequest)).to.deep.equal({ + floorMin: 0, + floorRuleValue: 2.2, + matchingFloor: 2.2, + matchingData: '/12345/sports/basketball', + matchingRule: '/12345/sports/basketball' + }); + }); + it('picks the gptSlot from the adUnit and does not call the slotMatching', function () { + const newBidRequest1 = { ...basicBidRequest, transactionId: 'au1' }; + adUnits = [{code: newBidRequest1.code, transactionId: 'au1'}]; + utils.deepSetValue(adUnits[0], 'ortb2Imp.ext.data.adserver', { + name: 'gam', + adslot: '/12345/news/politics' + }) + expect(getFirstMatchingFloor(gptFloorData, newBidRequest1)).to.deep.equal({ + floorMin: 0, + floorRuleValue: 3.3, + matchingFloor: 3.3, + matchingData: '/12345/news/politics', + matchingRule: '/12345/news/politics' + }); + + const newBidRequest2 = { ...basicBidRequest, adUnitCode: 'test_div_2', transactionId: 'au2' }; + adUnits = [{code: newBidRequest2.adUnitCode, transactionId: newBidRequest2.transactionId}]; + utils.deepSetValue(adUnits[0], 'ortb2Imp.ext.data.adserver', { + name: 'gam', + adslot: '/12345/news/weather' + }) + expect(getFirstMatchingFloor(gptFloorData, newBidRequest2)).to.deep.equal({ + floorMin: 0, + floorRuleValue: 4.4, + matchingFloor: 4.4, + matchingData: '/12345/news/weather', + matchingRule: '/12345/news/weather' + }); + }); + }); }); describe('pre-auction tests', function () { let exposedAdUnits; @@ -1486,17 +1576,12 @@ describe('the price floors module', function () { }); }); describe('bidResponseHook tests', function () { - let returnedBidResponse; - let bidderRequest = { - bidderCode: 'appnexus', - auctionId: '123456', - bids: [{ - bidder: 'appnexus', - adUnitCode: 'test_div_1', - auctionId: '123456', - bidId: '1111' - }] - }; + const AUCTION_ID = '123456'; + let returnedBidResponse, indexStub; + let adUnit = { + transactionId: 'au', + code: 'test_div_1' + } let basicBidResponse = { bidderCode: 'appnexus', width: 300, @@ -1504,38 +1589,46 @@ describe('the price floors module', function () { cpm: 0.5, mediaType: 'banner', requestId: '1111', + transactionId: 'au', }; beforeEach(function () { returnedBidResponse = {}; + indexStub = sinon.stub(auctionManager, 'index'); + indexStub.get(() => stubAuctionIndex({adUnits: [adUnit]})); }); + + afterEach(() => { + indexStub.restore(); + }); + function runBidResponse(bidResp = basicBidResponse) { let next = (adUnitCode, bid) => { returnedBidResponse = bid; }; - addBidResponseHook.bind({ bidderRequest })(next, bidResp.adUnitCode, bidResp); + addBidResponseHook(next, bidResp.adUnitCode, Object.assign(createBid(CONSTANTS.STATUS.GOOD, {auctionId: AUCTION_ID}), bidResp)); }; it('continues with the auction if not floors data is present without any flooring', function () { runBidResponse(); expect(returnedBidResponse).to.not.haveOwnProperty('floorData'); }); it('if no matching rule it should not floor and should call log warn', function () { - _floorDataForAuction[bidderRequest.auctionId] = utils.deepClone(basicFloorConfig); - _floorDataForAuction[bidderRequest.auctionId].data.values = { 'video': 1.0 }; + _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[AUCTION_ID].data.values = { 'video': 1.0 }; runBidResponse(); expect(returnedBidResponse).to.not.haveOwnProperty('floorData'); expect(logWarnSpy.calledOnce).to.equal(true); }); it('if it finds a rule and floors should update the bid accordingly', function () { - _floorDataForAuction[bidderRequest.auctionId] = utils.deepClone(basicFloorConfig); - _floorDataForAuction[bidderRequest.auctionId].data.values = { 'banner': 1.0 }; + _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[AUCTION_ID].data.values = { 'banner': 1.0 }; runBidResponse(); expect(returnedBidResponse).to.haveOwnProperty('floorData'); expect(returnedBidResponse.status).to.equal(CONSTANTS.BID_STATUS.BID_REJECTED); expect(returnedBidResponse.cpm).to.equal(0); }); it('if it finds a rule and does not floor should update the bid accordingly', function () { - _floorDataForAuction[bidderRequest.auctionId] = utils.deepClone(basicFloorConfig); - _floorDataForAuction[bidderRequest.auctionId].data.values = { 'banner': 0.3 }; + _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[AUCTION_ID].data.values = { 'banner': 0.3 }; runBidResponse(); expect(returnedBidResponse).to.haveOwnProperty('floorData'); expect(returnedBidResponse.floorData).to.deep.equal({ @@ -1557,7 +1650,7 @@ describe('the price floors module', function () { expect(returnedBidResponse.cpm).to.equal(0.5); }); it('if should work with more complex rules and update accordingly', function () { - _floorDataForAuction[bidderRequest.auctionId] = { + _floorDataForAuction[AUCTION_ID] = { ...basicFloorConfig, data: { currency: 'USD', @@ -1643,4 +1736,49 @@ describe('the price floors module', function () { expect(_floorDataForAuction[AUCTION_END_EVENT.auctionId]).to.be.undefined; }); }); + + describe('fieldMatchingFunctions', () => { + let sandbox; + + const req = { + ...basicBidRequest, + } + + const resp = { + transactionId: req.transactionId, + size: [100, 100], + mediaType: 'banner', + } + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(auctionManager, 'index').get(() => stubAuctionIndex({ + adUnits: [ + { + code: req.adUnitCode, + transactionId: req.transactionId, + ortb2Imp: {ext: {data: {adserver: {name: 'gam', adslot: 'slot'}}}} + } + ] + })); + }); + + afterEach(() => { + sandbox.restore(); + }) + + Object.entries({ + size: '100x100', + mediaType: resp.mediaType, + gptSlot: 'slot', + domain: 'localhost', + adUnitCode: req.adUnitCode, + }).forEach(([test, expected]) => { + describe(`${test}`, () => { + it('should work with only bidResponse', () => { + expect(fieldMatchingFunctions[test](undefined, resp)).to.eql(expected) + }) + }); + }) + }); }); diff --git a/test/spec/modules/projectLimeLightBidAdapter_spec.js b/test/spec/modules/projectLimeLightBidAdapter_spec.js deleted file mode 100644 index 778d8eedf7b..00000000000 --- a/test/spec/modules/projectLimeLightBidAdapter_spec.js +++ /dev/null @@ -1,259 +0,0 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/projectLimeLightBidAdapter.js'; - -describe('ProjectLimeLightAdapter', function () { - const bid1 = { - bidId: '2dd581a2b6281d', - bidder: 'project-limelight', - bidderRequestId: '145e1d6a7837c9', - params: { - host: 'ads.project-limelight.com', - adUnitId: 123, - adUnitType: 'banner' - }, - placementCode: 'placement_0', - auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - sizes: [[300, 250]], - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' - } - const bid2 = { - bidId: '58ee9870c3164a', - bidder: 'project-limelight', - bidderRequestId: '209fdaf1c81649', - params: { - host: 'cpm.project-limelight.com', - adUnitId: 456, - adUnitType: 'banner' - }, - placementCode: 'placement_1', - auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', - sizes: [[350, 200]], - transactionId: '068867d1-46ec-40bb-9fa0-e24611786fb4' - } - const bid3 = { - bidId: '019645c7d69460', - bidder: 'project-limelight', - bidderRequestId: 'f2b15f89e77ba6', - params: { - host: 'ads.project-limelight.com', - adUnitId: 789, - adUnitType: 'video' - }, - placementCode: 'placement_2', - auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', - sizes: [[800, 600]], - transactionId: '738d5915-6651-43b9-9b6b-d50517350917' - } - - describe('buildRequests', function () { - const serverRequests = spec.buildRequests([bid1, bid2, bid3]) - it('Creates two ServerRequests', function() { - expect(serverRequests).to.exist - expect(serverRequests).to.have.lengthOf(2) - }) - serverRequests.forEach(serverRequest => { - it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequest).to.exist - expect(serverRequest.method).to.exist - expect(serverRequest.url).to.exist - expect(serverRequest.data).to.exist - }) - it('Returns POST method', function () { - expect(serverRequest.method).to.equal('POST') - }) - it('Returns valid data if array of bids is valid', function () { - let data = serverRequest.data - expect(data).to.be.an('object') - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'secure', 'adUnits') - expect(data.deviceWidth).to.be.a('number') - expect(data.deviceHeight).to.be.a('number') - expect(data.secure).to.be.a('boolean') - data.adUnits.forEach(adUnit => { - expect(adUnit).to.have.all.keys('id', 'bidId', 'type', 'sizes', 'transactionId') - expect(adUnit.id).to.be.a('number') - expect(adUnit.bidId).to.be.a('string') - expect(adUnit.type).to.be.a('string') - expect(adUnit.transactionId).to.be.a('string') - expect(adUnit.sizes).to.be.an('array') - }) - }) - }) - it('Returns valid URL', function () { - expect(serverRequests[0].url).to.equal('https://ads.project-limelight.com/hb') - expect(serverRequests[1].url).to.equal('https://cpm.project-limelight.com/hb') - }) - it('Returns valid adUnits', function () { - validateAdUnit(serverRequests[0].data.adUnits[0], bid1) - validateAdUnit(serverRequests[1].data.adUnits[0], bid2) - validateAdUnit(serverRequests[0].data.adUnits[1], bid3) - }) - it('Returns empty data if no valid requests are passed', function () { - const serverRequests = spec.buildRequests([]) - expect(serverRequests).to.be.an('array').that.is.empty - }) - }) - describe('interpretBannerResponse', function () { - let resObject = { - body: [ { - requestId: '123', - mediaType: 'banner', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD' - } ] - }; - let serverResponses = spec.interpretResponse(resObject); - it('Returns an array of valid server responses if response object is valid', function () { - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - let dataItem = serverResponses[i]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - expect(dataItem.ad).to.be.a('string'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.mediaType).to.be.a('string'); - } - it('Returns an empty array if invalid response is passed', function () { - serverResponses = spec.interpretResponse('invalid_response'); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - }); - }); - describe('interpretVideoResponse', function () { - let resObject = { - body: [ { - requestId: '123', - mediaType: 'video', - cpm: 0.3, - width: 320, - height: 50, - vastXml: '', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD' - } ] - }; - let serverResponses = spec.interpretResponse(resObject); - it('Returns an array of valid server responses if response object is valid', function () { - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - let dataItem = serverResponses[i]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - expect(dataItem.vastXml).to.be.a('string'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.mediaType).to.be.a('string'); - } - it('Returns an empty array if invalid response is passed', function () { - serverResponses = spec.interpretResponse('invalid_response'); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - }); - }); - describe('isBidRequestValid', function() { - let bid = { - bidId: '2dd581a2b6281d', - bidder: 'project-limelight', - bidderRequestId: '145e1d6a7837c9', - params: { - adUnitId: 123, - adUnitType: 'banner' - }, - placementCode: 'placement_0', - auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - sizes: [[300, 250]], - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' - }; - - it('should return true when required params found', function() { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function() { - let bidFailed = { - bidder: 'project-limelight', - bidderRequestId: '145e1d6a7837c9', - params: { - adUnitId: 123, - adUnitType: 'banner' - }, - placementCode: 'placement_0', - auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - sizes: [[300, 250]], - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' - }; - expect(spec.isBidRequestValid(bidFailed)).to.equal(false); - }); - }); - describe('interpretResponse', function() { - let resObject = { - requestId: '123', - mediaType: 'banner', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD' - }; - it('should skip responses which do not contain required params', function() { - let bidResponses = { - body: [ { - mediaType: 'banner', - cpm: 0.3, - ttl: 1000, - currency: 'USD' - }, resObject ] - } - expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); - }); - it('should skip responses which do not contain expected mediaType', function() { - let bidResponses = { - body: [ { - requestId: '123', - mediaType: 'native', - cpm: 0.3, - creativeId: '123asd', - ttl: 1000, - currency: 'USD' - }, resObject ] - } - expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); - }); - }); -}); - -function validateAdUnit(adUnit, bid) { - expect(adUnit.id).to.equal(bid.params.adUnitId) - expect(adUnit.bidId).to.equal(bid.bidId) - expect(adUnit.type).to.equal(bid.params.adUnitType.toUpperCase()) - expect(adUnit.transactionId).to.equal(bid.transactionId) - expect(adUnit.sizes).to.deep.equal(bid.sizes.map(size => { - return { - width: size[0], - height: size[1] - } - })) -} diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index bdcdca06183..084c533b5b5 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -1,5 +1,8 @@ import { expect } from 'chai'; -let { spec } = require('modules/proxistoreBidAdapter'); +import { spec } from 'modules/proxistoreBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from '../../../src/config.js'; + const BIDDER_CODE = 'proxistore'; describe('ProxistoreBidAdapter', function () { const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; @@ -52,10 +55,11 @@ describe('ProxistoreBidAdapter', function () { }); describe('buildRequests', function () { const url = { - cookieBase: 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi', + cookieBase: 'https://abs.proxistore.com/v3/rtb/prebid/multi', cookieLess: - 'https://abs.cookieless-proxistore.com/fr/v3/rtb/prebid/multi', + 'https://abs.cookieless-proxistore.com/v3/rtb/prebid/multi', }; + let request = spec.buildRequests([bid], bidderRequest); it('should return a valid object', function () { expect(request).to.be.an('object'); @@ -91,11 +95,11 @@ describe('ProxistoreBidAdapter', function () { vendorData: { vendor: { consents: { - '418': true - } + 418: true, + }, }, }, - apiVersion: 2 + apiVersion: 2, }; // has gdpr consent request = spec.buildRequests([bid], bidderRequest); @@ -117,12 +121,11 @@ describe('ProxistoreBidAdapter', function () { let data = JSON.parse(request.data); expect(data.bids[0].floor).to.be.null; - // make it respond with a non USD floor should not send it - bid.getFloor = function () { - return { currency: 'EUR', floor: 1.0 }; - }; + bid.params['bidFloor'] = 1; let req = spec.buildRequests([bid], bidderRequest); data = JSON.parse(req.data); + // eslint-disable-next-line no-console + console.log(data.bids[0]); expect(data.bids[0].floor).equal(1); bid.getFloor = function () { return { currency: 'USD', floor: 1.0 }; diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 57b83fced06..4599eb2a6fa 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -173,7 +173,7 @@ describe('pubGENIUS adapter', () => { expectedRequest = { method: 'POST', - url: 'https://ortb.adpearl.io/prebid/auction', + url: 'https://auction.adpearl.io/prebid/auction', data: { id: 'fake-auction-id', imp: [ @@ -370,6 +370,8 @@ describe('pubGENIUS adapter', () => { protocols: [2, 3], api: [1, 2], playbackmethod: [3, 4], + maxduration: 10, + linearity: 1, }, }; bidRequest.params.video = { @@ -394,6 +396,7 @@ describe('pubGENIUS adapter', () => { skipafter: 1, playbackmethod: [3, 4], api: [1, 2], + linearity: 1, }; expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); @@ -490,7 +493,7 @@ describe('pubGENIUS adapter', () => { }; expectedSync = { type: 'iframe', - url: 'https://ortb.adpearl.io/usersync/pixels.html?', + url: 'https://auction.adpearl.io/usersync/pixels.html?', }; }); @@ -548,7 +551,7 @@ describe('pubGENIUS adapter', () => { onTimeout(timeoutData); expect(server.requests[0].method).to.equal('POST'); - expect(server.requests[0].url).to.equal('https://ortb.adpearl.io/prebid/events?type=timeout'); + expect(server.requests[0].url).to.equal('https://auction.adpearl.io/prebid/events?type=timeout'); expect(JSON.parse(server.requests[0].requestBody)).to.deep.equal(timeoutData); }); }); diff --git a/test/spec/modules/publinkIdSystem_spec.js b/test/spec/modules/publinkIdSystem_spec.js new file mode 100644 index 00000000000..cfb5f8ed135 --- /dev/null +++ b/test/spec/modules/publinkIdSystem_spec.js @@ -0,0 +1,169 @@ +import {publinkIdSubmodule} from 'modules/publinkIdSystem.js'; +import {getStorageManager} from '../../../src/storageManager'; +import {server} from 'test/mocks/xhr.js'; +import sinon from 'sinon'; +import {uspDataHandler} from '../../../src/adapterManager'; +import {parseUrl} from '../../../src/utils'; + +export const storage = getStorageManager(24); +const TEST_COOKIE_VALUE = 'cookievalue'; +describe('PublinkIdSystem', () => { + describe('decode', () => { + it('decode', () => { + const result = publinkIdSubmodule.decode(TEST_COOKIE_VALUE); + expect(result).deep.equals({publinkId: TEST_COOKIE_VALUE}); + }); + }); + + describe('Fetch Local Cookies', () => { + const PUBLINK_COOKIE = '_publink'; + const PUBLINK_SRV_COOKIE = '_publink_srv'; + const EXP = Date.now() + 60 * 60 * 24 * 7 * 1000; + const COOKIE_VALUE = {publink: 'publinkCookieValue', exp: EXP}; + const LOCAL_VALUE = {publink: 'publinkLocalStorageValue', exp: EXP}; + const COOKIE_EXPIRATION = (new Date(Date.now() + 60 * 60 * 24 * 1000)).toUTCString(); + const DELETE_COOKIE = 'Thu, 01 Jan 1970 00:00:01 GMT'; + it('publink srv cookie', () => { + storage.setCookie(PUBLINK_SRV_COOKIE, JSON.stringify(COOKIE_VALUE), COOKIE_EXPIRATION); + const result = publinkIdSubmodule.getId(); + expect(result.id).to.equal(COOKIE_VALUE.publink); + storage.setCookie(PUBLINK_SRV_COOKIE, '', DELETE_COOKIE); + }); + it('publink srv local storage', () => { + storage.setDataInLocalStorage(PUBLINK_SRV_COOKIE, JSON.stringify(LOCAL_VALUE)); + const result = publinkIdSubmodule.getId(); + expect(result.id).to.equal(LOCAL_VALUE.publink); + storage.removeDataFromLocalStorage(PUBLINK_SRV_COOKIE); + }); + it('publink cookie', () => { + storage.setCookie(PUBLINK_COOKIE, JSON.stringify(COOKIE_VALUE), COOKIE_EXPIRATION); + const result = publinkIdSubmodule.getId(); + expect(result.id).to.equal(COOKIE_VALUE.publink); + storage.setCookie(PUBLINK_COOKIE, '', DELETE_COOKIE); + }); + it('publink local storage', () => { + storage.setDataInLocalStorage(PUBLINK_COOKIE, JSON.stringify(LOCAL_VALUE)); + const result = publinkIdSubmodule.getId(); + expect(result.id).to.equal(LOCAL_VALUE.publink); + storage.removeDataFromLocalStorage(PUBLINK_COOKIE); + }); + it('priority goes to publink_srv cookie', () => { + storage.setCookie(PUBLINK_SRV_COOKIE, JSON.stringify(COOKIE_VALUE), COOKIE_EXPIRATION); + storage.setDataInLocalStorage(PUBLINK_COOKIE, JSON.stringify(LOCAL_VALUE)); + const result = publinkIdSubmodule.getId(); + expect(result.id).to.equal(COOKIE_VALUE.publink); + storage.setCookie(PUBLINK_SRV_COOKIE, '', DELETE_COOKIE); + storage.removeDataFromLocalStorage(PUBLINK_COOKIE); + }); + it('publink non-json cookie', () => { + storage.setCookie(PUBLINK_COOKIE, COOKIE_VALUE.publink, COOKIE_EXPIRATION); + const result = publinkIdSubmodule.getId(); + expect(result.id).to.equal(COOKIE_VALUE.publink); + storage.setCookie(PUBLINK_COOKIE, '', DELETE_COOKIE); + }); + }); + + describe('getId', () => { + const serverResponse = {publink: 'ec0xHT3yfAOnykP64Qf0ORSi7LjNT1wju04ZSCsoPBekOJdBwK-0Zl_lXKDNnzhauC4iszBc-PvA1Be6IMlh1QocA'}; + it('no config', () => { + const result = publinkIdSubmodule.getId(); + expect(result).to.exist; + expect(result.callback).to.be.a('function'); + }); + + it('Use local copy', () => { + const result = publinkIdSubmodule.getId({}, undefined, TEST_COOKIE_VALUE); + expect(result).to.be.undefined; + }); + + describe('callout for id', () => { + let callbackSpy = sinon.spy(); + + beforeEach(() => { + callbackSpy.resetHistory(); + }); + + it('Fetch with consent data', () => { + const config = {storage: {type: 'cookie'}, params: {e: 'ca11c0ca7', site_id: '102030'}}; + const consentData = {gdprApplies: 1, consentString: 'myconsentstring'}; + let submoduleCallback = publinkIdSubmodule.getId(config, consentData).callback; + submoduleCallback(callbackSpy); + + const request = server.requests[0]; + const parsed = parseUrl(request.url); + + expect(parsed.hostname).to.equal('proc.ad.cpe.dotomi.com'); + expect(parsed.pathname).to.equal('/cvx/client/sync/publink'); + expect(parsed.search.mpn).to.equal('Prebid.js'); + expect(parsed.search.mpv).to.equal('$prebid.version$'); + expect(parsed.search.gdpr).to.equal('1'); + expect(parsed.search.gdpr_consent).to.equal('myconsentstring'); + expect(parsed.search.sid).to.equal('102030'); + expect(parsed.search.apikey).to.be.undefined; + + request.respond(200, {}, JSON.stringify(serverResponse)); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.equal(serverResponse.publink); + }); + + it('server doesnt respond', () => { + const config = {storage: {type: 'cookie'}, params: {e: 'ca11c0ca7'}}; + let submoduleCallback = publinkIdSubmodule.getId(config).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + const parsed = parseUrl(request.url); + + expect(parsed.hostname).to.equal('proc.ad.cpe.dotomi.com'); + expect(parsed.pathname).to.equal('/cvx/client/sync/publink'); + expect(parsed.search.mpn).to.equal('Prebid.js'); + expect(parsed.search.mpv).to.equal('$prebid.version$'); + + request.respond(204, {}, JSON.stringify(serverResponse)); + expect(callbackSpy.called).to.be.false; + }); + + it('reject plain email address', () => { + const config = {storage: {type: 'cookie'}, params: {e: 'tester@test.com'}}; + const consentData = {gdprApplies: 1, consentString: 'myconsentstring'}; + let submoduleCallback = publinkIdSubmodule.getId(config, consentData).callback; + submoduleCallback(callbackSpy); + + expect(server.requests).to.have.lengthOf(0); + expect(callbackSpy.called).to.be.false; + }); + }); + + describe('usPrivacy', () => { + let callbackSpy = sinon.spy(); + const oldPrivacy = uspDataHandler.getConsentData(); + before(() => { + uspDataHandler.setConsentData('1YNN'); + }); + after(() => { + uspDataHandler.setConsentData(oldPrivacy); + callbackSpy.resetHistory(); + }); + + it('Fetch with usprivacy data', () => { + const config = {storage: {type: 'cookie'}, params: {e: 'ca11c0ca7', api_key: 'abcdefg'}}; + let submoduleCallback = publinkIdSubmodule.getId(config).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + const parsed = parseUrl(request.url); + + expect(parsed.hostname).to.equal('proc.ad.cpe.dotomi.com'); + expect(parsed.pathname).to.equal('/cvx/client/sync/publink'); + expect(parsed.search.mpn).to.equal('Prebid.js'); + expect(parsed.search.mpv).to.equal('$prebid.version$'); + expect(parsed.search.us_privacy).to.equal('1YNN'); + expect(parsed.search.apikey).to.equal('abcdefg'); + + request.respond(200, {}, JSON.stringify(serverResponse)); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.equal(serverResponse.publink); + }); + }); + }); +}); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 11de7b13208..b6268548ccc 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -1,4 +1,5 @@ import pubmaticAnalyticsAdapter from 'modules/pubmaticAnalyticsAdapter.js'; +import adapterManager from 'src/adapterManager.js'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; import { @@ -94,6 +95,9 @@ const BID2 = Object.assign({}, BID, { 'hb_pb': '1.500', 'hb_size': '728x90', 'hb_source': 'server' + }, + meta: { + advertiserDomains: ['example.com'] } }); @@ -302,6 +306,8 @@ describe('pubmatic analytics adapter', function () { }); it('Logger: best case + win tracker', function() { + this.timeout(5000) + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] }); @@ -342,6 +348,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); expect(data.s[0].ps[0].piid).to.equal('partnerImpressionID-1'); expect(data.s[0].ps[0].db).to.equal(0); @@ -366,6 +373,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); expect(data.s[1].ps[0].piid).to.equal('partnerImpressionID-2'); expect(data.s[1].ps[0].db).to.equal(0); @@ -377,6 +385,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -401,6 +410,7 @@ describe('pubmatic analytics adapter', function () { expect(decodeURIComponent(data.slot)).to.equal('/19968336/header-bid-tag-0'); expect(decodeURIComponent(data.kgpv)).to.equal('/19968336/header-bid-tag-0'); expect(data.pn).to.equal('pubmatic'); + expect(data.bc).to.equal('pubmatic'); expect(data.eg).to.equal('1.23'); expect(data.en).to.equal('1.23'); expect(data.piid).to.equal('partnerImpressionID-1'); @@ -409,6 +419,7 @@ describe('pubmatic analytics adapter', function () { it('bidCpmAdjustment: USD: Logger: best case + win tracker', function() { const bidCopy = utils.deepClone(BID); bidCopy.cpm = bidCopy.originalCpm * 2; // bidCpmAdjustment => bidCpm * 2 + this.timeout(5000) sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { return [bidCopy, MOCK.BID_RESPONSE[1]] @@ -441,6 +452,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); expect(data.s[0].ps[0].kgpv).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps[0].eg).to.equal(1.23); @@ -508,6 +520,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); expect(data.s[0].ps[0].kgpv).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps[0].eg).to.equal(1); @@ -551,6 +564,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); expect(data.s[1].ps[0].db).to.equal(1); expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); @@ -587,6 +601,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); expect(data.s[1].ps[0].db).to.equal(1); expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); @@ -629,6 +644,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); expect(data.s[1].ps[0].db).to.equal(0); expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); @@ -639,6 +655,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -685,6 +702,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); expect(data.s[1].ps[0].db).to.equal(0); expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); @@ -695,6 +713,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -730,6 +749,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); expect(data.s[1].ps[0].db).to.equal(0); expect(data.s[1].ps[0].kgpv).to.equal('*'); @@ -740,6 +760,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -757,9 +778,10 @@ describe('pubmatic analytics adapter', function () { expect(data.kgpv).to.equal('*'); }); - it('Logger: regexPattern in bid.bidResponse', function() { + it('Logger: regexPattern in bid.bidResponse and url in adomain', function() { const BID2_COPY = utils.deepClone(BID2); BID2_COPY.regexPattern = '*'; + BID2_COPY.meta.advertiserDomains = ['https://www.example.com/abc/223'] events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); @@ -783,6 +805,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); expect(data.s[1].ps[0].db).to.equal(0); expect(data.s[1].ps[0].kgpv).to.equal('*'); @@ -793,6 +816,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -833,6 +857,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); expect(data.s[1].ps[0].db).to.equal(0); expect(data.s[1].ps[0].kgpv).to.equal('*'); @@ -843,6 +868,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -885,6 +911,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); expect(data.s[1].ps[0].db).to.equal(0); expect(data.s[1].ps[0].kgpv).to.equal('*'); @@ -895,6 +922,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -910,5 +938,119 @@ describe('pubmatic analytics adapter', function () { firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); expect(data.kgpv).to.equal('*'); }); + + it('Logger: best case + win tracker in case of Bidder Aliases', function() { + MOCK.BID_REQUESTED['bids'][0]['bidder'] = 'pubmatic_alias'; + adapterManager.aliasRegistry['pubmatic_alias'] = 'pubmatic'; + + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] + }); + + config.setConfig({ + testGroupId: 15 + }); + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.pubid).to.equal('9999'); + expect(data.pid).to.equal('1111'); + expect(data.pdvid).to.equal('20'); + expect(data.iid).to.equal('25c6d7f5-699a-4bfc-87c9-996f915341fa'); + expect(data.to).to.equal('3000'); + expect(data.purl).to.equal('http://www.test.com/page.html'); + expect(data.orig).to.equal('www.test.com'); + expect(data.tst).to.equal(1519767016); + expect(data.tgid).to.equal(15); + expect(data.s).to.be.an('array'); + expect(data.s.length).to.equal(2); + + // slot 1 + expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].sz).to.deep.equal(['640x480']); + expect(data.s[0].ps).to.be.an('array'); + expect(data.s[0].ps.length).to.equal(1); + expect(data.s[0].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bc).to.equal('pubmatic_alias'); + expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); + expect(data.s[0].ps[0].piid).to.equal('partnerImpressionID-1'); + expect(data.s[0].ps[0].db).to.equal(0); + expect(data.s[0].ps[0].kgpv).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps[0].kgpsv).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps[0].psz).to.equal('640x480'); + expect(data.s[0].ps[0].eg).to.equal(1.23); + expect(data.s[0].ps[0].en).to.equal(1.23); + expect(data.s[0].ps[0].di).to.equal(''); + expect(data.s[0].ps[0].dc).to.equal(''); + expect(data.s[0].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l2).to.equal(0); + expect(data.s[0].ps[0].ss).to.equal(0); + expect(data.s[0].ps[0].t).to.equal(0); + expect(data.s[0].ps[0].wb).to.equal(1); + expect(data.s[0].ps[0].af).to.equal('video'); + expect(data.s[0].ps[0].ocpm).to.equal(1.23); + expect(data.s[0].ps[0].ocry).to.equal('USD'); + + // slot 2 + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].ps).to.be.an('array'); + expect(data.s[1].ps.length).to.equal(1); + expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bc).to.equal('pubmatic'); + expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); + expect(data.s[1].ps[0].piid).to.equal('partnerImpressionID-2'); + expect(data.s[1].ps[0].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].psz).to.equal('728x90'); + expect(data.s[1].ps[0].eg).to.equal(1.52); + expect(data.s[1].ps[0].en).to.equal(1.52); + expect(data.s[1].ps[0].di).to.equal('the-deal-id'); + expect(data.s[1].ps[0].dc).to.equal('PMP'); + expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); + expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[1].ps[0].l2).to.equal(0); + expect(data.s[1].ps[0].ss).to.equal(1); + expect(data.s[1].ps[0].t).to.equal(0); + expect(data.s[1].ps[0].wb).to.equal(1); + expect(data.s[1].ps[0].af).to.equal('banner'); + expect(data.s[1].ps[0].ocpm).to.equal(1.52); + expect(data.s[1].ps[0].ocry).to.equal('USD'); + + // tracker slot1 + let firstTracker = requests[0].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.pubid).to.equal('9999'); + expect(decodeURIComponent(data.purl)).to.equal('http://www.test.com/page.html'); + expect(data.tst).to.equal('1519767014'); + expect(data.iid).to.equal('25c6d7f5-699a-4bfc-87c9-996f915341fa'); + expect(data.bidid).to.equal('2ecff0db240757'); + expect(data.pid).to.equal('1111'); + expect(data.pdvid).to.equal('20'); + expect(decodeURIComponent(data.slot)).to.equal('/19968336/header-bid-tag-0'); + expect(decodeURIComponent(data.kgpv)).to.equal('/19968336/header-bid-tag-0'); + expect(data.pn).to.equal('pubmatic'); + expect(data.bc).to.equal('pubmatic_alias'); + expect(data.eg).to.equal('1.23'); + expect(data.en).to.equal('1.23'); + expect(data.piid).to.equal('partnerImpressionID-1'); + }); }); }); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 28e79a69e55..6261586f451 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from 'modules/pubmaticBidAdapter.js'; +import {spec, checkVideoPlacement} from 'modules/pubmaticBidAdapter.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; @@ -791,6 +791,228 @@ describe('PubMatic adapter', function () { isValid = spec.isBidRequestValid(validBid); expect(isValid).to.equal(true); }); + + it('should check for context if video is present', function() { + let bid = { + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890' + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }) + + it('should return false if context is not present in video', function() { + let bid = { + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890' + }, + 'mediaTypes': { + 'video': { + 'w': 640, + 'h': 480, + 'protocols': [1, 2, 5], + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }) + + it('bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array', function() { + let bid = { + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890', + 'video': {} + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }; + + delete bid.params.video.mimes; // Undefined + bid.mediaTypes.video.mimes = 'string'; // NOT array + expect(spec.isBidRequestValid(bid)).to.equal(false); + + delete bid.params.video.mimes; // Undefined + delete bid.mediaTypes.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); + + delete bid.params.video.mimes; // Undefined + bid.mediaTypes.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); + + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + bid.params.video = {mimes: 'string'}; // NOT array + expect(spec.isBidRequestValid(bid)).to.equal(false); + + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + delete bid.params.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); + + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + bid.params.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); + + delete bid.mediaTypes.video.mimes; // Undefined + bid.params.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); + + delete bid.mediaTypes.video.mimes; // Undefined + delete bid.params.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('checks on bid.params.outstreamAU & bid.renderer & bid.mediaTypes.video.renderer', function() { + const getThebid = function() { + let bid = utils.deepClone(validOutstreamBidRequest.bids[0]); + bid.params.outstreamAU = 'pubmatic-test'; + bid.renderer = ' '; // we are only checking if this key is set or not + bid.mediaTypes.video.renderer = ' '; // we are only checking if this key is set or not + return bid; + } + + // true: when all are present + // mdiatype: outstream + // bid.params.outstreamAU : Y + // bid.renderer : Y + // bid.mediaTypes.video.renderer : Y + let bid = getThebid(); + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : Y + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : Y + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : Y + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // false: none present; only outstream + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + // true: none present; outstream + Banner + // mdiatype: outstream, banner + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + bid.mediaTypes.banner = {sizes: [ [300, 250], [300, 600] ]}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: none present; outstream + Native + // mdiatype: outstream, native + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + bid.mediaTypes.native = {} + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); }); describe('Request formation', function () { @@ -858,8 +1080,6 @@ describe('PubMatic adapter', function () { expect(data.site.domain).to.be.a('string'); // domain should be set expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id - expect(data.site.ext).to.exist.and.to.be.an('object'); // dctr parameter - expect(data.site.ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude @@ -879,6 +1099,7 @@ describe('PubMatic adapter', function () { expect(data.imp[0].banner.w).to.equal(300); // width expect(data.imp[0].banner.h).to.equal(250); // height expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid + expect(data.imp[0].ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); }); @@ -967,7 +1188,6 @@ describe('PubMatic adapter', function () { expect(data.app.bundle).to.equal('org.prebid.mobile.demoapp'); expect(data.app.domain).to.equal('prebid.org'); expect(data.app.publisher.id).to.equal(bidRequests[0].params.publisherId); - expect(data.app.ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); expect(data.site).to.not.exist; sandbox.restore(); }); @@ -994,7 +1214,6 @@ describe('PubMatic adapter', function () { expect(data.app.bundle).to.equal('org.prebid.mobile.demoapp'); expect(data.app.domain).to.equal('prebid.org'); expect(data.app.publisher.id).to.equal(bidRequests[0].params.publisherId); - expect(data.app.ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); expect(data.app.content).to.deep.equal(content); expect(data.site).to.not.exist; sandbox.restore(); @@ -1026,7 +1245,6 @@ describe('PubMatic adapter', function () { expect(data.app.bundle).to.equal('org.prebid.mobile.demoapp'); expect(data.app.domain).to.equal('prebid.org'); expect(data.app.publisher.id).to.equal(bidRequests[0].params.publisherId); - expect(data.app.ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); expect(data.app.content).to.deep.equal(appContent); expect(data.site).to.not.exist; sandbox.restore(); @@ -1043,8 +1261,6 @@ describe('PubMatic adapter', function () { expect(data.site.domain).to.be.a('string'); // domain should be set expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id - expect(data.site.ext).to.exist.and.to.be.an('object'); // dctr parameter - expect(data.site.ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude @@ -1063,6 +1279,7 @@ describe('PubMatic adapter', function () { expect(data.imp[0].banner.w).to.equal(728); // width expect(data.imp[0].banner.h).to.equal(90); // height expect(data.imp[0].banner.format).to.deep.equal([{w: 160, h: 600}]); + expect(data.imp[0].ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); }); @@ -1348,6 +1565,69 @@ describe('PubMatic adapter', function () { expect(data2.regs).to.equal(undefined);// USP/CCPAs }); + it('Request params check with JW player params', function() { + let bidRequests = [ + { + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0', + dctr: 'key1=val1|key2=val2,val3' + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[300, 250], [300, 600]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + rtd: { + jwplayer: { + targeting: { + content: { id: 'jw_d9J2zcaA' }, + segments: ['80011026', '80011035'] + } + } + } + }]; + let key_val_output = 'key1=val1|key2=val2,val3|jw-id=jw_d9J2zcaA|jw-80011026=1|jw-80011035=1' + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.imp[0].ext).to.exist.and.to.be.an('object'); + expect(data.imp[0].ext.key_val).to.exist.and.to.equal(key_val_output); + + // jw player data not available. Only dctr sent. + delete bidRequests[0].rtd; + request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + + expect(data.imp[0].ext).to.exist.and.to.be.an('object'); // dctr parameter + expect(data.imp[0].ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); + + // jw player data is available, but dctr is not present + bidRequests[0].rtd = { + jwplayer: { + targeting: { + content: { id: 'jw_d9J2zcaA' }, + segments: ['80011026', '80011035'] + } + } + }; + + delete bidRequests[0].params.dctr; + key_val_output = 'jw-id=jw_d9J2zcaA|jw-80011026=1|jw-80011035=1'; + request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + + expect(data.imp[0].ext).to.exist.and.to.be.an('object'); + expect(data.imp[0].ext.key_val).to.exist.and.to.equal(key_val_output); + }); + describe('FPD', function() { let newRequest; @@ -1566,14 +1846,25 @@ describe('PubMatic adapter', function () { let newRequest; let floorModuleTestData; let getFloor = function(req) { - return floorModuleTestData[req.mediaType]; + // actual getFloor module does not work like this :) + // special treatment for banner since for other mediaTypes we pass * + if (req.mediaType === 'banner') { + return floorModuleTestData[req.mediaType][ req.size[0] + 'x' + req.size[1] ] || {}; + } + return floorModuleTestData[req.mediaType] || {}; }; beforeEach(() => { floorModuleTestData = { 'banner': { - 'currency': 'USD', - 'floor': 1.50 + '300x250': { + 'currency': 'USD', + 'floor': 1.50 + }, + '300x600': { + 'currency': 'USD', + 'floor': 2.0 + } }, 'video': { 'currency': 'USD', @@ -1589,7 +1880,7 @@ describe('PubMatic adapter', function () { }); it('bidfloor should be undefined if calculation is <= 0', function() { - floorModuleTestData.banner.floor = 0; // lowest of them all + floorModuleTestData.banner['300x250'].floor = 0; // lowest of them all newRequest[0].params.kadfloor = undefined; let request = spec.buildRequests(newRequest, { auctionId: 'new-auction-id' @@ -1600,7 +1891,8 @@ describe('PubMatic adapter', function () { }); it('ignore floormodule o/p if floor is not number', function() { - floorModuleTestData.banner.floor = 'INR'; + floorModuleTestData.banner['300x250'].floor = 'Not-a-Number'; + floorModuleTestData.banner['300x600'].floor = 'Not-a-Number'; newRequest[0].params.kadfloor = undefined; let request = spec.buildRequests(newRequest, { auctionId: 'new-auction-id' @@ -1611,7 +1903,8 @@ describe('PubMatic adapter', function () { }); it('ignore floormodule o/p if currency is not matched', function() { - floorModuleTestData.banner.currency = 'INR'; + floorModuleTestData.banner['300x250'].currency = 'INR'; + floorModuleTestData.banner['300x600'].currency = 'INR'; newRequest[0].params.kadfloor = undefined; let request = spec.buildRequests(newRequest, { auctionId: 'new-auction-id' @@ -1641,7 +1934,7 @@ describe('PubMatic adapter', function () { expect(data.bidfloor).to.equal(3); }); - it('kadfloor is passed as 1, use min of fllorModule as it is highest', function() { + it('kadfloor is passed as 1, use min of floorModule as it is highest', function() { newRequest[0].params.kadfloor = '1.0';// yes, we want it as a string let request = spec.buildRequests(newRequest, { auctionId: 'new-auction-id' @@ -2096,6 +2389,68 @@ describe('PubMatic adapter', function () { expect(data.user.eids).to.equal(undefined); }); }); + + describe('FlocId', function() { + it('send the FlocId if it is present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.flocId = { + id: '1234', + version: 'chrome1.1' + } + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'chrome.com', + 'uids': [{ + 'id': '1234', + 'atype': 1, + 'ext': { + 'ver': 'chrome1.1' + } + }] + }]); + expect(data.user.data).to.deep.equal([{ + id: 'FLOC', + name: 'FLOC', + ext: { + ver: 'chrome1.1' + }, + segment: [{ + id: '1234', + name: 'chrome.com', + value: '1234' + }] + }]); + }); + + it('appnend the flocId if userIds are present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.netId = 'netid-user-id'; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); + bidRequests[0].userId.flocId = { + id: '1234', + version: 'chrome1.1' + } + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'netid.de', + 'uids': [{ + 'id': 'netid-user-id', + 'atype': 1 + }] + }, { + 'source': 'chrome.com', + 'uids': [{ + 'id': '1234', + 'atype': 1, + 'ext': { + 'ver': 'chrome1.1' + } + }] + }]); + }); + }); }); it('Request params check for video ad', function () { @@ -2105,7 +2460,6 @@ describe('PubMatic adapter', function () { let data = JSON.parse(request.data); expect(data.imp[0].video).to.exist; expect(data.imp[0].tagid).to.equal('Div1'); - expect(data.imp[0].video.ext['video_skippable']).to.equal(videoBidRequests[0].params.video.skippable ? 1 : 0); expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); @@ -2175,7 +2529,6 @@ describe('PubMatic adapter', function () { // video imp object check expect(data.imp[1].video).to.exist; expect(data.imp[1].tagid).to.equal('Div1'); - expect(data.imp[1].video.ext['video_skippable']).to.equal(multipleMediaRequests[1].params.video.skippable ? 1 : 0); expect(data.imp[1]['video']['mimes']).to.exist.and.to.be.an('array'); expect(data.imp[1]['video']['mimes'][0]).to.equal(multipleMediaRequests[1].params.video['mimes'][0]); expect(data.imp[1]['video']['mimes'][1]).to.equal(multipleMediaRequests[1].params.video['mimes'][1]); @@ -2494,6 +2847,93 @@ describe('PubMatic adapter', function () { expect(data.video).to.exist; expect(data.native).to.not.exist; }); + + it('should build video impression if video params are present in adunit.mediaTypes instead of bid.params', function() { + let videoReq = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890', + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', + 'sizes': [ + [640, 480] + ], + 'bidId': '21b59b1353ba82', + 'bidderRequestId': '1a08245305e6dd', + 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + let request = spec.buildRequests(videoReq, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.video).to.exist; + }); + + it('should build video impression with overwriting video params present in adunit.mediaTypes with bid.params', function() { + let videoReq = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890', + 'video': { + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 5], + 'linearity': 1 + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', + 'sizes': [ + [640, 480] + ], + 'bidId': '21b59b1353ba82', + 'bidderRequestId': '1a08245305e6dd', + 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + let request = spec.buildRequests(videoReq, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.video.linearity).to.equal(1); + }); }); it('Request params dctr check', function () { @@ -2503,17 +2943,6 @@ describe('PubMatic adapter', function () { params: { publisherId: '301', adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'AUD', dctr: 'key1=val1|key2=val2,!val3' }, placementCode: '/19968336/header-bid-tag-1', @@ -2559,11 +2988,11 @@ describe('PubMatic adapter', function () { dctr is found in adunit[0] */ - expect(data.site.ext).to.exist.and.to.be.an('object'); // dctr parameter - expect(data.site.ext.key_val).to.exist.and.to.equal(multipleBidRequests[0].params.dctr); + expect(data.imp[0].ext).to.exist.and.to.be.an('object'); // dctr parameter + expect(data.imp[0].ext.key_val).to.exist.and.to.equal(multipleBidRequests[0].params.dctr); /* case 2 - - dctr not present in adunit[0] + dctr not present in adunit[0] but present in adunit[1] */ delete multipleBidRequests[0].params.dctr; request = spec.buildRequests(multipleBidRequests, { @@ -2571,7 +3000,9 @@ describe('PubMatic adapter', function () { }); data = JSON.parse(request.data); - expect(data.site.ext).to.not.exist; + expect(data.imp[0].ext).to.exist.and.to.deep.equal({}); + expect(data.imp[1].ext).to.exist.and.to.be.an('object'); // dctr parameter + expect(data.imp[1].ext.key_val).to.exist.and.to.equal(multipleBidRequests[1].params.dctr); /* case 3 - dctr is present in adunit[0], but is not a string value @@ -2582,7 +3013,7 @@ describe('PubMatic adapter', function () { }); data = JSON.parse(request.data); - expect(data.site.ext).to.not.exist; + expect(data.imp[0].ext).to.exist.and.to.deep.equal({}); }); it('Request params deals check', function () { @@ -2966,10 +3397,159 @@ describe('PubMatic adapter', function () { let response = spec.interpretResponse(bidResponses, request); expect(response[0].renderer).to.not.exist; }); + + it('should assign mediaType by reading bid.ext.mediaType', function() { + let newvideoRequests = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5670', + 'video': { + 'mimes': ['video/mp4'], + 'skippable': true, + 'protocols': [1, 2, 5], + 'linearity': 1 + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }]; + let newvideoBidResponses = { + 'body': { + 'id': '1621441141473', + 'cur': 'USD', + 'customdata': 'openrtb1', + 'ext': { + 'buyid': 'myBuyId' + }, + 'seatbid': [{ + 'bid': [{ + 'id': '2c95df014cfe97', + 'impid': '2c95df014cfe97', + 'price': 4.2, + 'cid': 'test1', + 'crid': 'test2', + 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", + 'w': 0, + 'h': 0, + 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', + 'ext': { + 'bidtype': 1 + } + }], + 'ext': { + 'buyid': 'myBuyId' + } + }] + }, + 'headers': {} + } + let newrequest = spec.buildRequests(newvideoRequests, { + auctionId: 'new-auction-id' + }); + let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); + expect(newresponse[0].mediaType).to.equal('video') + }) + + it('should assign mediaType even if bid.ext.mediaType does not exists', function() { + let newvideoRequests = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5670', + 'video': { + 'mimes': ['video/mp4'], + 'skippable': true, + 'protocols': [1, 2, 5], + 'linearity': 1 + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }]; + let newvideoBidResponses = { + 'body': { + 'id': '1621441141473', + 'cur': 'USD', + 'customdata': 'openrtb1', + 'ext': { + 'buyid': 'myBuyId' + }, + 'seatbid': [{ + 'bid': [{ + 'id': '2c95df014cfe97', + 'impid': '2c95df014cfe97', + 'price': 4.2, + 'cid': 'test1', + 'crid': 'test2', + 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", + 'w': 0, + 'h': 0, + 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420' + }], + 'ext': { + 'buyid': 'myBuyId' + } + }] + }, + 'headers': {} + } + let newrequest = spec.buildRequests(newvideoRequests, { + auctionId: 'new-auction-id' + }); + let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); + expect(newresponse[0].mediaType).to.equal('video') + }) }); describe('getUserSyncs', function() { - const syncurl_iframe = 'https://ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p=5670'; + const syncurl_iframe = 'https://ads.pubmatic.com/AdServer/js/user_sync.html?kdntuid=1&p=5670'; const syncurl_image = 'https://image8.pubmatic.com/AdServer/ImgSync?p=5670'; let sandbox; beforeEach(function () { @@ -3064,5 +3644,261 @@ describe('PubMatic adapter', function () { }]); }); }); + + describe('JW player segment data for S2S', function() { + let sandbox = sinon.sandbox.create(); + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function() { + sandbox.restore(); + }); + it('Should append JW player segment data to dctr values in auction endpoint', function() { + var videoAdUnit = { + 'bidderCode': 'pubmatic', + 'bids': [ + { + 'bidder': 'pubmatic', + 'params': { + 'publisherId': '156276', + 'adSlot': 'pubmatic_video2', + 'dctr': 'key1=123|key2=345', + 'pmzoneid': '1243', + 'video': { + 'mimes': ['video/mp4', 'video/x-flv'], + 'skippable': true, + 'minduration': 5, + 'maxduration': 30, + 'startdelay': 5, + 'playbackmethod': [1, 3], + 'api': [1, 2], + 'protocols': [2, 3], + 'battr': [13, 14], + 'linearity': 1, + 'placement': 2, + 'minbitrate': 10, + 'maxbitrate': 10 + } + }, + 'rtd': { + 'jwplayer': { + 'targeting': { + 'segments': ['80011026', '80011035'], + 'content': { + 'id': 'jw_d9J2zcaA' + } + } + } + }, + 'bid_id': '17a6771be26cc4', + 'ortb2Imp': { + 'ext': { + 'data': { + 'pbadslot': 'abcd', + 'jwTargeting': { + 'playerID': 'myElement1', + 'mediaID': 'd9J2zcaA' + } + } + } + } + } + ], + 'auctionStart': 1630923178417, + 'timeout': 1000, + 'src': 's2s' + } + + spec.transformBidParams(bidRequests[0].params, true, videoAdUnit); + expect(bidRequests[0].params.dctr).to.equal('key1:val1,val2|key2:val1|jw-id=jw_d9J2zcaA|jw-80011026=1|jw-80011035=1'); + }); + it('Should send only JW player segment data in auction endpoint, if dctr is missing', function() { + var videoAdUnit = { + 'bidderCode': 'pubmatic', + 'bids': [ + { + 'bidder': 'pubmatic', + 'params': { + 'publisherId': '156276', + 'adSlot': 'pubmatic_video2', + 'dctr': 'key1=123|key2=345', + 'pmzoneid': '1243', + 'video': { + 'mimes': ['video/mp4', 'video/x-flv'], + 'skippable': true, + 'minduration': 5, + 'maxduration': 30, + 'startdelay': 5, + 'playbackmethod': [1, 3], + 'api': [1, 2], + 'protocols': [2, 3], + 'battr': [13, 14], + 'linearity': 1, + 'placement': 2, + 'minbitrate': 10, + 'maxbitrate': 10 + } + }, + 'rtd': { + 'jwplayer': { + 'targeting': { + 'segments': ['80011026', '80011035'], + 'content': { + 'id': 'jw_d9J2zcaA' + } + } + } + }, + 'bid_id': '17a6771be26cc4', + 'ortb2Imp': { + 'ext': { + 'data': { + 'pbadslot': 'abcd', + 'jwTargeting': { + 'playerID': 'myElement1', + 'mediaID': 'd9J2zcaA' + } + } + } + } + } + ], + 'auctionStart': 1630923178417, + 'timeout': 1000, + 'src': 's2s' + } + + delete bidRequests[0].params.dctr; + spec.transformBidParams(bidRequests[0].params, true, videoAdUnit); + expect(bidRequests[0].params.dctr).to.equal('jw-id=jw_d9J2zcaA|jw-80011026=1|jw-80011035=1'); + }); + + it('Should not send any JW player segment data in auction endpoint, if it is not available', function() { + var videoAdUnit = { + 'bidderCode': 'pubmatic', + 'bids': [ + { + 'bidder': 'pubmatic', + 'params': { + 'publisherId': '156276', + 'adSlot': 'pubmatic_video2', + 'dctr': 'key1=123|key2=345', + 'pmzoneid': '1243', + 'video': { + 'mimes': ['video/mp4', 'video/x-flv'], + 'skippable': true, + 'minduration': 5, + 'maxduration': 30, + 'startdelay': 5, + 'playbackmethod': [1, 3], + 'api': [1, 2], + 'protocols': [2, 3], + 'battr': [13, 14], + 'linearity': 1, + 'placement': 2, + 'minbitrate': 10, + 'maxbitrate': 10 + } + }, + 'bid_id': '17a6771be26cc4', + 'ortb2Imp': { + 'ext': { + 'data': { + 'pbadslot': 'abcd', + 'jwTargeting': { + 'playerID': 'myElement1', + 'mediaID': 'd9J2zcaA' + } + } + } + } + } + ], + 'auctionStart': 1630923178417, + 'timeout': 1000, + 'src': 's2s' + } + spec.transformBidParams(bidRequests[0].params, true, videoAdUnit); + expect(bidRequests[0].params.dctr).to.equal('key1:val1,val2|key2:val1'); + }); + }) + + describe('Checking for Video.Placement property', function() { + let sandbox, utilsMock; + const adUnit = 'Div1'; + const msg_placement_missing = 'Video.Placement param missing for Div1'; + let videoData = { + battr: [6, 7], + skipafter: 15, + maxduration: 50, + context: 'instream', + playerSize: [640, 480], + skip: 0, + connectiontype: [1, 2, 6], + skipmin: 10, + minduration: 10, + mimes: ['video/mp4', 'video/x-flv'], + } + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logWarn'); + }); + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }) + + it('should log Video.Placement param missing', function() { + checkVideoPlacement(videoData, adUnit); + sinon.assert.calledWith(utils.logWarn, msg_placement_missing); + }) + it('shoud not log Video.Placement param missing', function() { + videoData['placement'] = 1; + checkVideoPlacement(videoData, adUnit); + sinon.assert.neverCalledWith(utils.logWarn, msg_placement_missing); + }) + }); + }); + + describe('Video request params', function() { + let sandbox, utilsMock, newVideoRequest; + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logWarn'); + newVideoRequest = utils.deepClone(videoBidRequests) + }); + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }) + + it('Should log warning if video params from mediaTypes and params obj of bid are not present', function () { + delete newVideoRequest[0].mediaTypes.video; + delete newVideoRequest[0].params.video; + + let request = spec.buildRequests(newVideoRequest, { + auctionId: 'new-auction-id' + }); + + sinon.assert.calledOnce(utils.logWarn); + expect(request).to.equal(undefined); + }); + + it('Should consider video params from mediaType object of bid', function () { + delete newVideoRequest[0].params.video; + + let request = spec.buildRequests(newVideoRequest, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + expect(data.imp[0]['video']['battr']).to.equal(undefined); + }); }); }); diff --git a/test/spec/modules/pubxBidAdapter_spec.js b/test/spec/modules/pubxBidAdapter_spec.js index 6cea8787845..06bb5b5f638 100644 --- a/test/spec/modules/pubxBidAdapter_spec.js +++ b/test/spec/modules/pubxBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/pubxBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; describe('pubxAdapter', function () { const adapter = newBidder(spec); @@ -134,6 +135,9 @@ describe('pubxAdapter', function () { currency: 'JPY', height: 250, width: 300, + adomains: [ + 'test.com' + ], } } @@ -156,7 +160,12 @@ describe('pubxAdapter', function () { creativeId: 'TKmB', netRevenue: true, ttl: 300, - ad: '
some creative
' + ad: '
some creative
', + meta: { + advertiserDomains: [ + 'test.com' + ] + }, } ]; it('should return empty array when required param is empty', function () { @@ -184,6 +193,7 @@ describe('pubxAdapter', function () { expect(result.netRevenue).to.equal(bidResponses[0].netRevenue); expect(result.ttl).to.equal(bidResponses[0].ttl); expect(result.ad).to.equal(bidResponses[0].ad); + expect(result.meta.advertiserDomains).deep.to.equal(bidResponses[0].meta.advertiserDomains); }); }); }); diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 40f7afeeb6a..63364b867be 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -28,6 +28,7 @@ describe('pubxai analytics adapter', function() { }; let location = utils.getWindowLocation(); + let storage = window.top['sessionStorage']; let prebidEvent = { 'auctionInit': { @@ -514,6 +515,11 @@ describe('pubxai analytics adapter', function() { 'path': location.pathname, 'search': location.search }, + 'pmcDetail': { + 'bidDensity': storage.getItem('pbx:dpbid'), + 'maxBid': storage.getItem('pbx:mxbid'), + 'auctionId': storage.getItem('pbx:aucid') + } }; let expectedAfterBid = { @@ -521,6 +527,7 @@ describe('pubxai analytics adapter', function() { 'bidderCode': 'appnexus', 'bidId': '248f9a4489835e', 'adUnitCode': '/19968336/header-bid-tag-1', + 'gptSlotCode': utils.getGptSlotInfoForAdUnitCode('/19968336/header-bid-tag-1').gptSlot || null, 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', 'sizes': '300x250', 'renderStatus': 2, @@ -576,12 +583,18 @@ describe('pubxai analytics adapter', function() { 'deviceOS': getOS(), 'browser': getBrowser() }, + 'pmcDetail': { + 'bidDensity': storage.getItem('pbx:dpbid'), + 'maxBid': storage.getItem('pbx:mxbid'), + 'auctionId': storage.getItem('pbx:aucid') + }, 'initOptions': initOptions }; let expectedAfterBidWon = { 'winningBid': { 'adUnitCode': '/19968336/header-bid-tag-1', + 'gptSlotCode': utils.getGptSlotInfoForAdUnitCode('/19968336/header-bid-tag-1').gptSlot || null, 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', 'bidderCode': 'appnexus', 'bidId': '248f9a4489835e', @@ -623,6 +636,11 @@ describe('pubxai analytics adapter', function() { 'statusMessage': 'Bid available', 'timeToRespond': 267 }, + 'pageDetail': { + 'host': location.host, + 'path': location.pathname, + 'search': location.search + }, 'deviceDetail': { 'platform': navigator.platform, 'deviceType': getDeviceType(), diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index c3830d5cb46..92f7aa0b70d 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -254,7 +254,7 @@ describe('PulsePoint Adapter Tests', function () { expect(bid.ttl).to.equal(20); }); - it('Verify ttl/currency applied to bid', function () { + it('Verify ttl/currency/adomain applied to bid', function () { const request = spec.buildRequests(slotConfigs, bidderRequest); const ortbRequest = request.data; const ortbResponse = { @@ -264,7 +264,8 @@ describe('PulsePoint Adapter Tests', function () { price: 1.25, adm: 'This is an Ad#1', crid: 'Creative#123', - exp: 50 + exp: 50, + adomain: ['advertiser.com'] }, { impid: ortbRequest.imp[1].id, price: 1.25, @@ -282,11 +283,15 @@ describe('PulsePoint Adapter Tests', function () { expect(bid.ad).to.equal('This is an Ad#1'); expect(bid.ttl).to.equal(50); expect(bid.currency).to.equal('GBP'); + expect(bid.meta).to.not.be.null; + expect(bid.meta.advertiserDomains).to.eql(['advertiser.com']); const secondBid = bids[1]; expect(secondBid.cpm).to.equal(1.25); expect(secondBid.ad).to.equal('This is an Ad#2'); expect(secondBid.ttl).to.equal(20); expect(secondBid.currency).to.equal('GBP'); + expect(secondBid.meta).to.not.be.null; + expect(secondBid.meta.advertiserDomains).to.eql([]); }); it('Verify full passback', function () { @@ -632,7 +637,7 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.user.ext).to.not.be.undefined; expect(ortbRequest.user.ext.eids).to.not.be.undefined; expect(ortbRequest.user.ext.eids).to.have.lengthOf(2); - expect(ortbRequest.user.ext.eids[0].source).to.equal('pubcommon'); + expect(ortbRequest.user.ext.eids[0].source).to.equal('pubcid.org'); expect(ortbRequest.user.ext.eids[0].uids).to.have.lengthOf(1); expect(ortbRequest.user.ext.eids[0].uids[0].id).to.equal('userid_pubcid'); expect(ortbRequest.user.ext.eids[1].source).to.equal('adserver.org'); @@ -654,6 +659,16 @@ describe('PulsePoint Adapter Tests', function () { parrableId: { eid: 'parrable_id234' }, lipb: { lipbid: 'liveintent_id123' + }, + haloId: { + haloId: 'halo_user1' + }, + lotamePanoramaId: 'lotame_user2', + merkleId: 'merkle_user3', + fabrickId: 'fabrick_user4', + connectid: 'connect_user5', + uid2: { + id: 'uid2_user6' } }; const userVerify = function(obj, source, id) { @@ -672,13 +687,19 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.user).to.not.be.undefined; expect(ortbRequest.user.ext).to.not.be.undefined; expect(ortbRequest.user.ext.eids).to.not.be.undefined; - expect(ortbRequest.user.ext.eids).to.have.lengthOf(6); + expect(ortbRequest.user.ext.eids).to.have.lengthOf(12); userVerify(ortbRequest.user.ext.eids[0], 'britepool.com', 'britepool_id123'); - userVerify(ortbRequest.user.ext.eids[1], 'criteo', 'criteo_id234'); - userVerify(ortbRequest.user.ext.eids[2], 'identityLink', 'idl_id123'); + userVerify(ortbRequest.user.ext.eids[1], 'criteo.com', 'criteo_id234'); + userVerify(ortbRequest.user.ext.eids[2], 'liveramp.com', 'idl_id123'); userVerify(ortbRequest.user.ext.eids[3], 'id5-sync.com', 'id5id_234'); userVerify(ortbRequest.user.ext.eids[4], 'parrable.com', 'parrable_id234'); - userVerify(ortbRequest.user.ext.eids[5], 'liveintent.com', 'liveintent_id123'); + userVerify(ortbRequest.user.ext.eids[5], 'neustar.biz', 'fabrick_user4'); + userVerify(ortbRequest.user.ext.eids[6], 'audigent.com', 'halo_user1'); + userVerify(ortbRequest.user.ext.eids[7], 'merkleinc.com', 'merkle_user3'); + userVerify(ortbRequest.user.ext.eids[8], 'crwdcntrl.net', 'lotame_user2'); + userVerify(ortbRequest.user.ext.eids[9], 'verizonmedia.com', 'connect_user5'); + userVerify(ortbRequest.user.ext.eids[10], 'uidapi.com', 'uid2_user6'); + userVerify(ortbRequest.user.ext.eids[11], 'liveintent.com', 'liveintent_id123'); }); it('Verify multiple adsizes', function () { const bidRequests = deepClone(slotConfigs); @@ -778,4 +799,59 @@ describe('PulsePoint Adapter Tests', function () { const secondBid = bids[1]; expect(secondBid.vastXml).to.equal(''); }); + it('Verify bid floor', function () { + const bidRequests = deepClone(slotConfigs); + bidRequests[0].params.bidfloor = 1.05; + let request = spec.buildRequests(bidRequests, bidderRequest); + let ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp[0].bidfloor).to.equal(1.05); + expect(ortbRequest.imp[1].bidfloor).to.be.undefined; + let floorArg = null; + // publisher uses the floor module + bidRequests[0].getFloor = (arg) => { + floorArg = arg; + return { floor: 1.25 }; + }; + bidRequests[1].getFloor = () => { + return { floor: 2.05 }; + }; + request = spec.buildRequests(bidRequests, bidderRequest); + ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp[0].bidfloor).to.equal(1.25); + expect(ortbRequest.imp[1].bidfloor).to.equal(2.05); + expect(floorArg).to.not.be.null; + expect(floorArg.mediaType).to.equal('banner'); + expect(floorArg.currency).to.equal('USD'); + expect(floorArg.size).to.equal('*'); + }); + it('Verify Video params on mediaTypes.video', function () { + const bidRequests = deepClone(videoSlotConfig); + bidRequests[0].mediaTypes = { + video: { + w: 600, + h: 400, + minduration: 15, + maxduration: 20, + startdelay: 10, + skip: 0, + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video).to.not.be.null; + expect(ortbRequest.imp[0].native).to.be.null; + expect(ortbRequest.imp[0].banner).to.be.null; + expect(ortbRequest.imp[0].video.w).to.equal(600); + expect(ortbRequest.imp[0].video.h).to.equal(400); + expect(ortbRequest.imp[0].video.minduration).to.equal(15); + expect(ortbRequest.imp[0].video.maxduration).to.equal(20); + expect(ortbRequest.imp[0].video.startdelay).to.equal(10); + expect(ortbRequest.imp[0].video.skip).to.equal(0); + expect(ortbRequest.imp[0].video.minbitrate).to.equal(200); + expect(ortbRequest.imp[0].video.protocols).to.eql([1, 2, 4]); + }); }); diff --git a/test/spec/modules/pxyzBidAdapter_spec.js b/test/spec/modules/pxyzBidAdapter_spec.js index 6d8c6056076..21dd252c909 100644 --- a/test/spec/modules/pxyzBidAdapter_spec.js +++ b/test/spec/modules/pxyzBidAdapter_spec.js @@ -191,11 +191,15 @@ describe('pxyzBidAdapter', function () { 'mediaType': 'banner', 'currency': 'AUD', 'ttl': 300, - 'netRevenue': true + 'netRevenue': true, + 'meta': { + advertiserDomains: ['pg.xyz'] + } } ]; let result = spec.interpretResponse({ body: response }, {bidderRequest}); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].meta.advertiserDomains).to.deep.equal(expectedResponse[0].meta.advertiserDomains); }); it('handles nobid response', function () { diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index 5b4e7963e60..5e0d129581c 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -47,16 +47,15 @@ describe('Quantcast adapter', function () { storage.setCookie('__qca', '', 'Thu, 01 Jan 1970 00:00:00 GMT'); }); - function setupVideoBidRequest(videoParams) { + function setupVideoBidRequest(videoParams, mediaTypesParams) { bidRequest.params = { publisherId: 'test-publisher', // REQUIRED - Publisher ID provided by Quantcast // Video object as specified in OpenRTB 2.5 video: videoParams }; - bidRequest['mediaTypes'] = { - video: { - context: 'instream', - playerSize: [600, 300] + if (mediaTypesParams) { + bidRequest['mediaTypes'] = { + video: mediaTypesParams } } }; @@ -175,6 +174,69 @@ describe('Quantcast adapter', function () { delivery: [1], // optional placement: 1, // optional api: [2, 3] // optional + }, { + context: 'instream', + playerSize: [600, 300] + }); + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const expectedVideoBidRequest = { + publisherId: QUANTCAST_TEST_PUBLISHER, + requestId: '2f7b179d443f14', + imp: [ + { + video: { + mimes: ['video/mp4'], + minduration: 3, + maxduration: 5, + protocols: [3], + startdelay: 1, + linearity: 1, + battr: [1, 2], + maxbitrate: 10, + playbackmethod: [1], + delivery: [1], + placement: 1, + api: [2, 3], + w: 600, + h: 300 + }, + placementCode: 'div-gpt-ad-1438287399331-0', + bidFloor: 1e-10 + } + ], + site: { + page: 'http://example.com/hello.html', + referrer: 'http://example.com/hello.html', + domain: 'example.com' + }, + bidId: '2f7b179d443f14', + gdprSignal: 0, + uspSignal: 0, + coppa: 0, + prebidJsVersion: '$prebid.version$', + fpa: '' + }; + + expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); + }); + + it('sends video bid requests containing all the required parameters from mediaTypes', function() { + setupVideoBidRequest(null, { + mimes: ['video/mp4'], // required + minduration: 3, // optional + maxduration: 5, // optional + protocols: [3], // optional + startdelay: 1, // optional + linearity: 1, // optinal + battr: [1, 2], // optional + maxbitrate: 10, // optional + playbackmethod: [1], // optional + delivery: [1], // optional + placement: 1, // optional + api: [2, 3], // optional + context: 'instream', + playerSize: [600, 300] }); const requests = qcSpec.buildRequests([bidRequest], bidderRequest); @@ -222,6 +284,9 @@ describe('Quantcast adapter', function () { it('overrides video parameters with parameters from adunit', function() { setupVideoBidRequest({ mimes: ['video/mp4'] + }, { + context: 'instream', + playerSize: [600, 300] }); bidRequest.mediaTypes.video.mimes = ['video/webm']; @@ -257,7 +322,10 @@ describe('Quantcast adapter', function () { }); it('sends video bid request when no video parameters are given', function () { - setupVideoBidRequest(null); + setupVideoBidRequest(null, { + context: 'instream', + playerSize: [600, 300] + }); const requests = qcSpec.buildRequests([bidRequest], bidderRequest); const expectedVideoBidRequest = { @@ -658,7 +726,10 @@ describe('Quantcast adapter', function () { '
Quantcast
', creativeId: 1001, width: 300, - height: 250 + height: 250, + meta: { + advertiserDomains: ['dailymail.com'] + } } ] }; @@ -718,7 +789,10 @@ describe('Quantcast adapter', function () { ttl: QUANTCAST_TTL, creativeId: 1001, netRevenue: QUANTCAST_NET_REVENUE, - currency: 'USD' + currency: 'USD', + meta: { + advertiserDomains: ['dailymail.com'] + } }; const interpretedResponse = qcSpec.interpretResponse(response); @@ -738,7 +812,10 @@ describe('Quantcast adapter', function () { creativeId: 1001, netRevenue: QUANTCAST_NET_REVENUE, currency: 'USD', - dealId: 'test-dealid' + dealId: 'test-dealid', + meta: { + advertiserDomains: ['dailymail.com'] + } }; const interpretedResponse = qcSpec.interpretResponse(response); diff --git a/test/spec/modules/quantcastIdSystem_spec.js b/test/spec/modules/quantcastIdSystem_spec.js index 12c8689fd3f..e9d44dd6124 100644 --- a/test/spec/modules/quantcastIdSystem_spec.js +++ b/test/spec/modules/quantcastIdSystem_spec.js @@ -1,15 +1,25 @@ -import { quantcastIdSubmodule, storage } from 'modules/quantcastIdSystem.js'; +import { quantcastIdSubmodule, storage, firePixel, hasCCPAConsent, hasGDPRConsent, checkTCFv2 } from 'modules/quantcastIdSystem.js'; +import * as utils from 'src/utils.js'; +import {coppaDataHandler} from 'src/adapterManager'; describe('QuantcastId module', function () { beforeEach(function() { - storage.setCookie('__qca', '', 'Thu, 01 Jan 1970 00:00:00 GMT'); + sinon.stub(coppaDataHandler, 'getCoppa'); + sinon.stub(utils, 'triggerPixel'); + sinon.stub(window, 'addEventListener'); }); - it('getId() should return a quantcast id when the Quantcast first party cookie exists', function () { - storage.setCookie('__qca', 'P0-TestFPA'); + afterEach(function () { + utils.triggerPixel.restore(); + coppaDataHandler.getCoppa.restore(); + window.addEventListener.restore(); + }); + it('getId() should return a quantcast id when the Quantcast first party cookie exists', function () { + sinon.stub(storage, 'getCookie').returns('P0-TestFPA'); const id = quantcastIdSubmodule.getId(); expect(id).to.be.deep.equal({id: {quantcastId: 'P0-TestFPA'}}); + storage.getCookie.restore(); }); it('getId() should return an empty id when the Quantcast first party cookie is missing', function () { @@ -17,3 +27,357 @@ describe('QuantcastId module', function () { expect(id).to.be.deep.equal({id: undefined}); }); }); + +describe('QuantcastId fire pixel', function () { + beforeEach(function () { + storage.setCookie('__qca', '', 'Thu, 01 Jan 1970 00:00:00 GMT'); + sinon.stub(storage, 'setCookie'); + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + utils.triggerPixel.restore(); + storage.setCookie.restore(); + }); + + it('fpa should be set when not present on this call', function () { + firePixel('clientId'); + var urlString = utils.triggerPixel.getCall(0).args[0]; + var parsedUrl = utils.parseUrl(urlString); + var urlSearchParams = parsedUrl.search; + assert.equal(urlSearchParams.fpan, '1'); + assert.notEqual(urlSearchParams.fpa, null); + }); + + it('fpa should be extracted from the Quantcast first party cookie when present on this call', function () { + sinon.stub(storage, 'getCookie').returns('P0-TestFPA'); + firePixel('clientId'); + var urlString = utils.triggerPixel.getCall(0).args[0]; + var parsedUrl = utils.parseUrl(urlString); + var urlSearchParams = parsedUrl.search; + assert.equal(urlSearchParams.fpan, '0'); + assert.equal(urlSearchParams.fpa, 'P0-TestFPA'); + storage.getCookie.restore(); + }); + + it('function to trigger pixel is called once', function () { + firePixel('clientId'); + expect(utils.triggerPixel.calledOnce).to.equal(true); + }); + + it('function to trigger pixel is not called when client id is absent', function () { + firePixel(); + expect(utils.triggerPixel.calledOnce).to.equal(false); + }); +}); + +describe('Quantcast CCPA consent check', function() { + it('returns true when CCPA constent string is not present', function() { + expect(hasCCPAConsent()).to.equal(true); + }); + + it("returns true when notice_given or do-not-sell in CCPA constent string is not 'Y' ", function() { + expect(hasCCPAConsent('1NNN')).to.equal(true); + expect(hasCCPAConsent('1YNN')).to.equal(true); + expect(hasCCPAConsent('1NYN')).to.equal(true); + }); + + it("returns false when CCPA consent string is present, and notice_given or do-not-sell in the string is 'Y' ", function() { + expect(hasCCPAConsent('1YYN')).to.equal(false); + }); +}); + +describe('Quantcast GDPR consent check', function() { + it("returns true when GDPR doesn't apply", function() { + expect(hasGDPRConsent({gdprApplies: false})).to.equal(true); + }); + + it('returns false if denied consent, even if special purpose 1 treatment is true in DE', function() { + expect(checkTCFv2({ + gdprApplies: true, + publisherCC: 'DE', + purposeOneTreatment: true, + vendor: { + consents: { '11': false } + }, + purpose: { + consents: { '1': false } + }, + publisher: { + restrictions: { + '1': { + '11': 0 // flatly disallow Quantcast + } + } + } + }, ['1'])).to.equal(false); + }); + + it('returns false if publisher flatly denies required purpose', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': true } + }, + purpose: { + consents: { '1': true } + }, + publisher: { + restrictions: { + '1': { + '11': 0 // flatly disallow Quantcast + } + } + } + }, ['1'])).to.equal(false); + }); + + it('returns true if positive consent for required purpose', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': true } + }, + purpose: { + consents: { '1': true } + } + }, ['1'])).to.equal(true); + }); + + it('returns false if positive consent but publisher requires legitimate interest for required purpose', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': true } + }, + purpose: { + consents: { '1': true } + }, + publisher: { + restrictions: { + '1': { + '11': 2 // require legitimate interest for Quantcast + } + } + } + }, ['1'])).to.equal(false); + }); + + it('returns false if no vendor consent and no legitimate interest', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': false } + }, + purpose: { + consents: { '1': true } + } + }, ['1'])).to.equal(false); + }); + + it('returns false if no purpose consent and no legitimate interest', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': true } + }, + purpose: { + consents: { '1': false } + } + }, ['1'])).to.equal(false); + }); + + it('returns false if no consent, but legitimate interest for consent-first purpose, and no restrictions specified', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': true }, + legitimateInterests: { '11': true } + }, + purpose: { + consents: { '1': false }, + legitimateInterests: { '1': true } + } + }, ['1'])).to.equal(false); + }); + + it('returns false if consent, but no legitimate interest for legitimate-interest-first purpose, and no restrictions specified', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': true }, + legitimateInterests: { '11': true } + }, + purpose: { + consents: { '10': true }, + legitimateInterests: { '10': false } + } + }, ['10'])).to.equal(false); + }); + + it('returns true if consent, but no legitimate interest for legitimate-interest-first purpose, and corresponding consent restriction specified', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': true }, + legitimateInterests: { '11': true } + }, + purpose: { + consents: { '10': true }, + legitimateInterests: { '10': false } + }, + publisher: { + restrictions: { + '10': { + '11': 1 // require consent for Quantcast + } + } + } + }, ['10'])).to.equal(true); + }); + + it('returns false if no consent but legitimate interest for required purpose other than 1, but publisher requires consent', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': false }, + legitimateInterests: { '11': true } + }, + purpose: { + consents: { '10': false }, + legitimateInterests: { '10': true } + }, + publisher: { + restrictions: { + '10': { + '11': 1 // require consent for Quantcast + } + } + } + }, ['10'])).to.equal(false); + }); + + it('returns false if no consent and no legitimate interest for vendor for required purpose other than 1', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': false }, + legitimateInterests: { '11': false } + }, + purpose: { + consents: { '10': false }, + legitimateInterests: { '10': true } + } + }, ['10'])).to.equal(false); + }); + + it('returns false if no consent and no legitimate interest for required purpose other than 1', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': false }, + legitimateInterests: { '11': true } + }, + purpose: { + consents: { '10': false }, + legitimateInterests: { '10': false } + } + }, ['10'])).to.equal(false); + }); + + it('returns false if no consent but legitimate interest for required purpose, but required purpose is purpose 1', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': false }, + legitimateInterests: { '11': true } + }, + purpose: { + consents: { '1': false }, + legitimateInterests: { '1': true } + } + }, ['1'])).to.equal(false); + }); + + it('returns true if different legal bases for multiple required purposes', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': true }, + legitimateInterests: { '11': true } + }, + purpose: { + consents: { + '1': true, + '10': false + }, + legitimateInterests: { + '1': false, + '10': true + } + }, + publisher: { + restrictions: { + '10': { + '11': 2 // require legitimate interest for Quantcast + } + } + } + })).to.equal(true); + }); + + it('returns true if full consent and legitimate interest for all required purposes with no restrictions specified', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': true }, + legitimateInterests: { '11': true } + }, + purpose: { + consents: { + '1': true, + '3': true, + '7': true, + '8': true, + '9': true, + '10': true + }, + legitimateInterests: { + '1': true, + '3': true, + '7': true, + '8': true, + '9': true, + '10': true + } + } + })).to.equal(true); + }); + + it('returns false if one of multiple required purposes has no legal basis', function() { + expect(checkTCFv2({ + gdprApplies: true, + vendor: { + consents: { '11': true }, + legitimateInterests: { '11': true } + }, + purpose: { + consents: { + '1': true, + '10': false + }, + legitimateInterests: { + '11': false, + '10': true + } + }, + publisher: { + restrictions: { + '10': { + '11': 1 // require consent for Quantcast + } + } + } + })).to.equal(false); + }); +}); diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js index 5d297d32014..560206681ee 100644 --- a/test/spec/modules/qwarryBidAdapter_spec.js +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -5,9 +5,19 @@ import { newBidder } from 'src/adapters/bidderFactory.js' const REQUEST = { 'bidId': '456', 'bidder': 'qwarry', + 'sizes': [[100, 200], [300, 400]], 'params': { zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7 + }, + 'schain': { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'qwarry.com', + sid: '00001', + hp: 1 + }] } } @@ -23,7 +33,8 @@ const BIDDER_BANNER_RESPONSE = { 'creativeId': 1, 'netRevenue': true, 'winUrl': 'http://test.com', - 'format': 'banner' + 'format': 'banner', + 'adomain': ['test.com'] }] } @@ -39,7 +50,8 @@ const BIDDER_VIDEO_RESPONSE = { 'creativeId': 2, 'netRevenue': true, 'winUrl': 'http://test.com', - 'format': 'video' + 'format': 'video', + 'adomain': ['test.com'] }] } @@ -85,7 +97,8 @@ describe('qwarryBidAdapter', function () { expect(bidderRequest.method).to.equal('POST') expect(bidderRequest.data.requestId).to.equal('123') expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') - expect(bidderRequest.data.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7 }) + expect(bidderRequest.data.schain).to.deep.contains({ver: '1.0', complete: 1, nodes: [{asi: 'qwarry.com', sid: '00001', hp: 1}]}) + expect(bidderRequest.data.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, sizes: [{ width: 100, height: 200 }, { width: 300, height: 400 }] }) expect(bidderRequest.data.gdprConsent).to.deep.contains({ consentRequired: true, consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' }) expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) expect(bidderRequest.options.contentType).to.equal('application/json') @@ -108,6 +121,8 @@ describe('qwarryBidAdapter', function () { expect(result[0]).to.have.property('netRevenue').equal(true) expect(result[0]).to.have.property('winUrl').equal('http://test.com') expect(result[0]).to.have.property('format').equal('banner') + expect(result[0].meta).to.exist.property('advertiserDomains') + expect(result[0].meta).to.have.property('advertiserDomains').lengthOf(1) }) it('handles video request : should get correct bid response', function () { @@ -125,6 +140,8 @@ describe('qwarryBidAdapter', function () { expect(result[0]).to.have.property('winUrl').equal('http://test.com') expect(result[0]).to.have.property('format').equal('video') expect(result[0]).to.have.property('vastXml').equal('vast') + expect(result[0].meta).to.exist.property('advertiserDomains') + expect(result[0].meta).to.have.property('advertiserDomains').lengthOf(1) }) it('handles no bid response : should get empty array', function () { diff --git a/test/spec/modules/radsBidAdapter_spec.js b/test/spec/modules/radsBidAdapter_spec.js index c3c3b4b2746..271f7cb1147 100644 --- a/test/spec/modules/radsBidAdapter_spec.js +++ b/test/spec/modules/radsBidAdapter_spec.js @@ -59,9 +59,24 @@ describe('radsAdapter', function () { 'sizes': [ [300, 250] ], + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + }, + 'banner': { + 'sizes': [ + [100, 100], [400, 400], [500, 500] + ] + } + }, 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' + 'auctionId': '1d1a030790a475', + 'userId': { + 'netId': '123', + 'uid2': '456' + } }, { 'bidder': 'rads', 'params': { @@ -78,7 +93,7 @@ describe('radsAdapter', function () { }, 'mediaTypes': { 'video': { - 'playerSize': [640, 480], + 'playerSize': [[640, 480], [500, 500], [600, 600]], 'context': 'instream' } }, @@ -110,13 +125,13 @@ describe('radsAdapter', function () { it('sends bid request to our endpoint via GET', function () { expect(request[0].method).to.equal('GET'); let data = request[0].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); - expect(data).to.equal('rt=bid-response&_f=prebid_js&_ps=6682&srw=300&srh=250&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&bcat=IAB2%2CIAB4&dvt=desktop&i=1.1.1.1'); + expect(data).to.equal('_f=prebid_js&_ps=6682&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&rt=bid-response&srw=100&srh=100&alt_ad_sizes%5B0%5D=400x400&alt_ad_sizes%5B1%5D=500x500&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&bcat=IAB2%2CIAB4&dvt=desktop&i=1.1.1.1&did_netid=123&did_uid2=456'); }); it('sends bid video request to our rads endpoint via GET', function () { expect(request[1].method).to.equal('GET'); let data = request[1].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); - expect(data).to.equal('rt=vast2&_f=prebid_js&_ps=6682&srw=640&srh=480&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgeo%5D%5Bregion%5D=DE-BE&bcat=IAB2%2CIAB4&dvt=desktop'); + expect(data).to.equal('_f=prebid_js&_ps=6682&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&rt=vast2&srw=640&srh=480&alt_ad_sizes%5B0%5D=500x500&alt_ad_sizes%5B1%5D=600x600&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgeo%5D%5Bregion%5D=DE-BE&bcat=IAB2%2CIAB4&dvt=desktop'); }); // with gdprConsent @@ -124,13 +139,13 @@ describe('radsAdapter', function () { it('sends bid request to our endpoint via GET', function () { expect(request2[0].method).to.equal('GET'); let data = request2[0].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); - expect(data).to.equal('rt=bid-response&_f=prebid_js&_ps=6682&srw=300&srh=250&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&i=1.1.1.1'); + expect(data).to.equal('_f=prebid_js&_ps=6682&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&rt=bid-response&srw=100&srh=100&alt_ad_sizes%5B0%5D=400x400&alt_ad_sizes%5B1%5D=500x500&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&i=1.1.1.1&did_netid=123&did_uid2=456'); }); it('sends bid video request to our rads endpoint via GET', function () { expect(request2[1].method).to.equal('GET'); let data = request2[1].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); - expect(data).to.equal('rt=vast2&_f=prebid_js&_ps=6682&srw=640&srh=480&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgeo%5D%5Bregion%5D=DE-BE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop'); + expect(data).to.equal('_f=prebid_js&_ps=6682&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&rt=vast2&srw=640&srh=480&alt_ad_sizes%5B0%5D=500x500&alt_ad_sizes%5B1%5D=600x600&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgeo%5D%5Bregion%5D=DE-BE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop'); }); }); @@ -146,7 +161,8 @@ describe('radsAdapter', function () { 'currency': 'EUR', 'ttl': 60, 'netRevenue': true, - 'zone': '6682' + 'zone': '6682', + 'adomain': ['bdomain'] } }; let serverVideoResponse = { @@ -174,7 +190,8 @@ describe('radsAdapter', function () { currency: 'EUR', netRevenue: true, ttl: 300, - ad: '' + ad: '', + meta: {advertiserDomains: ['bdomain']} }, { requestId: '23beaa6af6cdde', cpm: 0.5, @@ -186,7 +203,8 @@ describe('radsAdapter', function () { netRevenue: true, ttl: 300, vastXml: '{"reason":7001,"status":"accepted"}', - mediaType: 'video' + mediaType: 'video', + meta: {advertiserDomains: []} }]; it('should get the correct bid response by display ad', function () { @@ -202,6 +220,8 @@ describe('radsAdapter', function () { }]; let result = spec.interpretResponse(serverBannerResponse, bidRequest[0]); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].meta.advertiserDomains.length).to.equal(1); + expect(result[0].meta.advertiserDomains[0]).to.equal(expectedResponse[0].meta.advertiserDomains[0]); }); it('should get the correct rads video bid response by display ad', function () { @@ -220,6 +240,7 @@ describe('radsAdapter', function () { }]; let result = spec.interpretResponse(serverVideoResponse, bidRequest[0]); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[1])); + expect(result[0].meta.advertiserDomains.length).to.equal(0); }); it('handles empty bid response', function () { @@ -286,4 +307,29 @@ describe('radsAdapter', function () { expect(userSync[2].type).to.be.equal('image'); }); }); + + describe(`getUserSyncs test usage passback response`, function () { + let serverResponses; + + beforeEach(function () { + serverResponses = [{ + body: { + reason: 8002, + status: 'rejected', + msg: 'passback', + bid_id: '115de76437d5ae6', + 'zone': '4773', + } + }]; + }); + + it(`check for zero array when iframeEnabled`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(0); + }); + it(`check for zero array when iframeEnabled`, function () { + expect(spec.getUserSyncs({ pixelEnabled: true })).to.be.an('array'); + expect(spec.getUserSyncs({ pixelEnabled: true }, serverResponses).length).to.be.equal(0); + }); + }); }); diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js index b84aef15feb..11639733661 100644 --- a/test/spec/modules/realTimeDataModule_spec.js +++ b/test/spec/modules/realTimeDataModule_spec.js @@ -1,6 +1,9 @@ import * as rtdModule from 'modules/rtdModule/index.js'; -import { config } from 'src/config.js'; +import {config} from 'src/config.js'; import * as sinon from 'sinon'; +import {default as CONSTANTS} from '../../../src/constants.json'; +import {default as events} from '../../../src/events.js'; +import 'src/prebid.js'; const getBidRequestDataSpy = sinon.spy(); @@ -58,33 +61,136 @@ const conf = { }; describe('Real time module', function () { - before(function () { - rtdModule.attachRealTimeDataProvider(validSM); - rtdModule.attachRealTimeDataProvider(invalidSM); - rtdModule.attachRealTimeDataProvider(failureSM); - rtdModule.attachRealTimeDataProvider(nonConfSM); - rtdModule.attachRealTimeDataProvider(validSMWait); - }); + let eventHandlers; + let sandbox; - after(function () { - config.resetConfig(); - }); + function mockEmitEvent(event, ...args) { + (eventHandlers[event] || []).forEach((h) => h(...args)); + } - beforeEach(function () { - config.setConfig(conf); + before(() => { + eventHandlers = {}; + sandbox = sinon.sandbox.create(); + sandbox.stub(events, 'on').callsFake((event, handler) => { + if (!eventHandlers.hasOwnProperty(event)) { + eventHandlers[event] = []; + } + eventHandlers[event].push(handler); + }); }); - it('should use only valid modules', function () { - rtdModule.init(config); - expect(rtdModule.subModules).to.eql([validSMWait, validSM]); + after(() => { + sandbox.restore(); }); - it('should be able to modify bid request', function (done) { - rtdModule.setBidRequestsData(() => { - assert(getBidRequestDataSpy.calledTwice); - assert(getBidRequestDataSpy.calledWith({bidRequest: {}})); + describe('', () => { + const PROVIDERS = [validSM, invalidSM, failureSM, nonConfSM, validSMWait]; + let _detachers; + + beforeEach(function () { + _detachers = PROVIDERS.map(rtdModule.attachRealTimeDataProvider); + rtdModule.init(config); + config.setConfig(conf); + }); + + afterEach(function () { + _detachers.forEach((f) => f()); + config.resetConfig(); + }); + + it('should use only valid modules', function () { + expect(rtdModule.subModules).to.eql([validSMWait, validSM]); + }); + + it('should be able to modify bid request', function (done) { + rtdModule.setBidRequestsData(() => { + assert(getBidRequestDataSpy.calledTwice); + assert(getBidRequestDataSpy.calledWith({bidRequest: {}})); + done(); + }, {bidRequest: {}}) + }); + + it('sould place targeting on adUnits', function (done) { + const auction = { + adUnitCodes: ['ad1', 'ad2'], + adUnits: [ + { + code: 'ad1' + }, + { + code: 'ad2', + adserverTargeting: {preKey: 'preValue'} + } + ] + }; + + const expectedAdUnits = [ + { + code: 'ad1', + adserverTargeting: {key: 'validSMWait'} + }, + { + code: 'ad2', + adserverTargeting: { + preKey: 'preValue', + key: 'validSM' + } + } + ]; + + const adUnits = rtdModule.getAdUnitTargeting(auction); + assert.deepEqual(expectedAdUnits, adUnits) done(); - }, {bidRequest: {}}) + }); + + describe('setBidRequestData', () => { + let withWait, withoutWait; + + function runSetBidRequestData() { + return new Promise((resolve) => { + rtdModule.setBidRequestsData(resolve, {bidRequest: {}}); + }); + } + + beforeEach(() => { + withWait = { + submod: validSMWait, + cbTime: 0, + cbRan: false + }; + withoutWait = { + submod: validSM, + cbTime: 0, + cbRan: false + }; + + [withWait, withoutWait].forEach((c) => { + c.submod.getBidRequestData = sinon.stub().callsFake((_, cb) => { + setTimeout(() => { + c.cbRan = true; + cb(); + }, c.cbTime); + }); + }); + }); + + it('should allow non-priority submodules to run synchronously', () => { + withWait.cbTime = withoutWait.cbTime = 0; + return runSetBidRequestData().then(() => { + expect(withWait.cbRan).to.be.true; + expect(withoutWait.cbRan).to.be.true; + }) + }); + + it('should not wait for non-priority submodules if priority ones complete first', () => { + withWait.cbTime = 10; + withoutWait.cbTime = 100; + return runSetBidRequestData().then(() => { + expect(withWait.cbRan).to.be.true; + expect(withoutWait.cbRan).to.be.false; + }); + }); + }); }); it('deep merge object', function () { @@ -125,36 +231,78 @@ describe('Real time module', function () { assert.deepEqual(expected, merged); }); - it('sould place targeting on adUnits', function (done) { - const auction = { - adUnitCodes: ['ad1', 'ad2'], - adUnits: [ - { - code: 'ad1' - }, - { - code: 'ad2', - adserverTargeting: {preKey: 'preValue'} - } - ] + describe('event', () => { + const EVENTS = { + [CONSTANTS.EVENTS.AUCTION_INIT]: 'onAuctionInitEvent', + [CONSTANTS.EVENTS.AUCTION_END]: 'onAuctionEndEvent', + [CONSTANTS.EVENTS.BID_RESPONSE]: 'onBidResponseEvent', + [CONSTANTS.EVENTS.BID_REQUESTED]: 'onBidRequestEvent' + } + const conf = { + 'realTimeData': { + dataProviders: [ + { + 'name': 'tp1', + }, + { + 'name': 'tp2', + } + ] + } }; + let providers; + let _detachers; - const expectedAdUnits = [ - { - code: 'ad1', - adserverTargeting: {key: 'validSMWait'} - }, - { - code: 'ad2', - adserverTargeting: { - preKey: 'preValue', - key: 'validSM' - } + function eventHandlingProvider(name) { + const provider = { + name: name, + init: () => true, } - ]; + Object.values(EVENTS).forEach((ev) => provider[ev] = sinon.spy()); + return provider; + } + + beforeEach(() => { + providers = [eventHandlingProvider('tp1'), eventHandlingProvider('tp2')]; + _detachers = providers.map(rtdModule.attachRealTimeDataProvider); + rtdModule.init(config); + config.setConfig(conf); + }); - const adUnits = rtdModule.getAdUnitTargeting(auction); - assert.deepEqual(expectedAdUnits, adUnits) - done(); - }) + afterEach(() => { + _detachers.forEach((d) => d()) + config.resetConfig(); + }); + + it('should set targeting for auctionEnd', () => { + providers.forEach(p => p.getTargetingData = sinon.spy()); + const auction = { + adUnitCodes: ['a1'], + adUnits: [{code: 'a1'}] + }; + mockEmitEvent(CONSTANTS.EVENTS.AUCTION_END, auction); + providers.forEach(p => { + expect(p.getTargetingData.calledWith(auction.adUnitCodes)).to.be.true; + }); + }); + + Object.entries(EVENTS).forEach(([event, hook]) => { + it(`'${event}' should be propagated to providers through '${hook}'`, () => { + const eventArg = {}; + mockEmitEvent(event, eventArg); + providers.forEach((provider) => { + const providerConf = conf.realTimeData.dataProviders.find((cfg) => cfg.name === provider.name); + expect(provider[hook].called).to.be.true; + expect(provider[hook].args).to.have.length(1); + expect(provider[hook].args[0]).to.include.members([eventArg, providerConf]) + }) + }); + + it(`${event} should not fail to propagate elsewhere if a provider throws in its event handler`, () => { + providers[0][hook] = function () { throw new Error() }; + mockEmitEvent(event); + expect(providers[1][hook].called).to.be.true; + }); + }); + }); }); diff --git a/test/spec/modules/reklamstoreBidAdapter_spec.js b/test/spec/modules/reklamstoreBidAdapter_spec.js deleted file mode 100644 index 1dcd6c17ca4..00000000000 --- a/test/spec/modules/reklamstoreBidAdapter_spec.js +++ /dev/null @@ -1,85 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/reklamstoreBidAdapter.js'; - -describe('reklamstoreBidAdapterTests', function() { - let bidRequestData = { - bids: [ - { - bidder: 'reklamstore', - params: { - regionId: 532211 - }, - sizes: [[300, 250]] - } - ] - }; - let request = []; - - it('validate_params', function() { - expect( - spec.isBidRequestValid({ - bidder: 'reklamstore', - params: { - regionId: 532211 - } - }) - ).to.equal(true); - }); - - it('validate_generated_params', function() { - let bidderRequest = { - refererInfo: { - referer: 'https://reklamstore.com' - } - }; - request = spec.buildRequests(bidRequestData.bids, bidderRequest); - let req_data = request[0].data; - - expect(req_data.regionId).to.equal(532211); - }); - - const serverResponse = { - body: - { - cpm: 1.2, - ad: 'Ad html', - w: 300, - h: 250, - syncs: [{ - type: 'image', - url: 'https://link1' - }, - { - type: 'iframe', - url: 'https://link2' - } - ] - } - }; - - it('validate_response_params', function() { - let bids = spec.interpretResponse(serverResponse, bidRequestData.bids[0]); - expect(bids).to.have.lengthOf(1); - - let bid = bids[0]; - expect(bid.ad).to.equal('Ad html'); - expect(bid.cpm).to.equal(1.2); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.currency).to.equal('USD'); - }); - - it('should return no syncs when pixel syncing is disabled', function () { - const syncs = spec.getUserSyncs({ pixelEnabled: false }, [serverResponse]); - expect(syncs).to.deep.equal([]); - }); - - it('should return user syncs', function () { - const syncs = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: true}, [serverResponse]); - const expected = [ - { type: 'image', url: 'https://link1' }, - { type: 'iframe', url: 'https://link2' }, - ]; - expect(syncs).to.deep.equal(expected); - }); -}); diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index ebc62752f16..f0d381ee3ed 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/relaidoBidAdapter.js'; import * as utils from 'src/utils.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { getStorageManager } from '../../../src/storageManager.js'; const UUID_KEY = 'relaido_uuid'; @@ -19,8 +20,12 @@ describe('RelaidoAdapter', function () { let bidderRequest; let serverResponse; let serverRequest; + let generateUUIDStub; + let triggerPixelStub; beforeEach(function () { + generateUUIDStub = sinon.stub(utils, 'generateUUID').returns(relaido_uuid); + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); bidRequest = { bidder: 'relaido', params: { @@ -52,25 +57,42 @@ describe('RelaidoAdapter', function () { serverResponse = { body: { status: 'ok', - price: 500, - model: 'vcpm', - currency: 'JPY', - creativeId: 1000, - uuid: relaido_uuid, - vast: '', + ads: [{ + placementId: 100000, + width: 640, + height: 360, + bidId: '2ed93003f7bb99', + price: 500, + model: 'vcpm', + currency: 'JPY', + creativeId: 1000, + vast: '', + syncUrl: 'https://relaido/sync.html', + adomain: ['relaido.co.jp', 'www.cmertv.co.jp'], + mediaType: 'video' + }], playerUrl: 'https://relaido/player.js', - syncUrl: 'https://relaido/sync.html' + syncUrl: 'https://api-dev.ulizaex.com/tr/v1/prebid/sync.html', + uuid: relaido_uuid, } }; serverRequest = { - method: 'GET', - bidId: bidRequest.bidId, - width: bidRequest.mediaTypes.video.playerSize[0][0], - height: bidRequest.mediaTypes.video.playerSize[0][1], - mediaType: 'video', + method: 'POST', + data: { + bids: [{ + bidId: bidRequest.bidId, + width: bidRequest.mediaTypes.video.playerSize[0][0], + height: bidRequest.mediaTypes.video.playerSize[0][1], + mediaType: 'video'}] + } }; }); + afterEach(() => { + generateUUIDStub.restore(); + triggerPixelStub.restore(); + }); + describe('spec.isBidRequestValid', function () { it('should return true when the required params are passed by video', function () { expect(spec.isBidRequestValid(bidRequest)).to.equal(true); @@ -184,51 +206,89 @@ describe('RelaidoAdapter', function () { describe('spec.buildRequests', function () { it('should build bid requests by video', function () { const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; - expect(request.method).to.equal('GET'); - expect(request.url).to.equal('https://api.relaido.jp/bid/v1/prebid/100000'); - expect(request.bidId).to.equal(bidRequest.bidId); - expect(request.width).to.equal(bidRequest.mediaTypes.video.playerSize[0][0]); - expect(request.height).to.equal(bidRequest.mediaTypes.video.playerSize[0][1]); - expect(request.mediaType).to.equal('video'); - expect(request.data.ref).to.equal(bidderRequest.refererInfo.referer); - expect(request.data.timeout_ms).to.equal(bidderRequest.timeout); - expect(request.data.ad_unit_code).to.equal(bidRequest.adUnitCode); - expect(request.data.auction_id).to.equal(bidRequest.auctionId); - expect(request.data.bidder).to.equal(bidRequest.bidder); - expect(request.data.bidder_request_id).to.equal(bidRequest.bidderRequestId); - expect(request.data.bid_requests_count).to.equal(bidRequest.bidRequestsCount); - expect(request.data.bid_id).to.equal(bidRequest.bidId); - expect(request.data.transaction_id).to.equal(bidRequest.transactionId); - expect(request.data.media_type).to.equal('video'); - expect(request.data.uuid).to.equal(relaido_uuid); - expect(request.data.width).to.equal(bidRequest.mediaTypes.video.playerSize[0][0]); - expect(request.data.height).to.equal(bidRequest.mediaTypes.video.playerSize[0][1]); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const request = data.bids[0]; + expect(bidRequests.method).to.equal('POST'); + expect(bidRequests.url).to.equal('https://api.relaido.jp/bid/v1/sprebid'); + expect(data.ref).to.equal(bidderRequest.refererInfo.referer); + expect(data.timeout_ms).to.equal(bidderRequest.timeout); + expect(request.ad_unit_code).to.equal(bidRequest.adUnitCode); + expect(request.auction_id).to.equal(bidRequest.auctionId); + expect(data.bidder).to.equal(bidRequest.bidder); + expect(request.bidder_request_id).to.equal(bidRequest.bidderRequestId); + expect(data.bid_requests_count).to.equal(bidRequest.bidRequestsCount); + expect(request.bid_id).to.equal(bidRequest.bidId); + expect(request.transaction_id).to.equal(bidRequest.transactionId); + expect(request.media_type).to.equal('video'); + expect(data.uuid).to.equal(relaido_uuid); + expect(data.pv).to.equal('$prebid.version$'); }); it('should build bid requests by banner', function () { + setUAMobile(); bidRequest.mediaTypes = { + video: { + context: 'outstream', + playerSize: [ + [320, 180] + ] + }, banner: { sizes: [ - [640, 360] + [640, 360], + [1, 1] ] } }; const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; - expect(request.mediaType).to.equal('banner'); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const request = data.bids[0]; + expect(request.media_type).to.equal('banner'); + }); + + it('should take 1x1 size', function () { + setUAMobile(); + bidRequest.mediaTypes = { + video: { + context: 'outstream', + playerSize: [ + [320, 180] + ] + }, + banner: { + sizes: [ + [640, 360], + [1, 1] + ] + } + }; + const bidRequests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const request = data.bids[0]; + + expect(request.width).to.equal(1); }); it('The referrer should be the last', function () { const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; - const keys = Object.keys(request.data); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const keys = Object.keys(data); expect(keys[0]).to.equal('version'); expect(keys[keys.length - 1]).to.equal('ref'); }); + + it('should get imuid', function () { + bidRequest.userId = {} + bidRequest.userId.imuid = 'i.tjHcK_7fTcqnbrS_YA2vaw'; + const bidRequests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + expect(data.imuid).to.equal('i.tjHcK_7fTcqnbrS_YA2vaw'); + }); }); describe('spec.interpretResponse', function () { @@ -236,33 +296,54 @@ describe('RelaidoAdapter', function () { const bidResponses = spec.interpretResponse(serverResponse, serverRequest); expect(bidResponses).to.have.lengthOf(1); const response = bidResponses[0]; - expect(response.requestId).to.equal(serverRequest.bidId); - expect(response.width).to.equal(serverRequest.width); - expect(response.height).to.equal(serverRequest.height); - expect(response.cpm).to.equal(serverResponse.body.price); - expect(response.currency).to.equal(serverResponse.body.currency); - expect(response.creativeId).to.equal(serverResponse.body.creativeId); - expect(response.vastXml).to.equal(serverResponse.body.vast); + expect(response.requestId).to.equal(serverRequest.data.bids[0].bidId); + expect(response.width).to.equal(serverRequest.data.bids[0].width); + expect(response.height).to.equal(serverRequest.data.bids[0].height); + expect(response.cpm).to.equal(serverResponse.body.ads[0].price); + expect(response.currency).to.equal(serverResponse.body.ads[0].currency); + expect(response.creativeId).to.equal(serverResponse.body.ads[0].creativeId); + expect(response.vastXml).to.equal(serverResponse.body.ads[0].vast); + expect(response.playerUrl).to.equal(serverResponse.body.playerUrl); + expect(response.meta.advertiserDomains).to.equal(serverResponse.body.ads[0].adomain); + expect(response.meta.mediaType).to.equal(VIDEO); expect(response.ad).to.be.undefined; }); it('should build bid response by banner', function () { - serverRequest.mediaType = 'banner'; + serverResponse.body.ads[0].mediaType = 'banner'; const bidResponses = spec.interpretResponse(serverResponse, serverRequest); expect(bidResponses).to.have.lengthOf(1); const response = bidResponses[0]; - expect(response.requestId).to.equal(serverRequest.bidId); - expect(response.width).to.equal(serverRequest.width); - expect(response.height).to.equal(serverRequest.height); - expect(response.cpm).to.equal(serverResponse.body.price); - expect(response.currency).to.equal(serverResponse.body.currency); - expect(response.creativeId).to.equal(serverResponse.body.creativeId); + expect(response.requestId).to.equal(serverRequest.data.bids[0].bidId); + expect(response.width).to.equal(serverRequest.data.bids[0].width); + expect(response.height).to.equal(serverRequest.data.bids[0].height); + expect(response.cpm).to.equal(serverResponse.body.ads[0].price); + expect(response.currency).to.equal(serverResponse.body.ads[0].currency); + expect(response.creativeId).to.equal(serverResponse.body.ads[0].creativeId); expect(response.vastXml).to.be.undefined; + expect(response.playerUrl).to.equal(serverResponse.body.playerUrl); expect(response.ad).to.include(`
`); expect(response.ad).to.include(``); expect(response.ad).to.include(`window.RelaidoPlayer.renderAd`); }); + it('should build bid response by video and playerUrl in ads', function () { + serverResponse.body.ads[0].playerUrl = 'https://relaido/player-customized.js'; + const bidResponses = spec.interpretResponse(serverResponse, serverRequest); + expect(bidResponses).to.have.lengthOf(1); + const response = bidResponses[0]; + expect(response.playerUrl).to.equal(serverResponse.body.ads[0].playerUrl); + }); + + it('should build bid response by banner and playerUrl in ads', function () { + serverResponse.body.ads[0].playerUrl = 'https://relaido/player-customized.js'; + serverResponse.body.ads[0].mediaType = 'banner'; + const bidResponses = spec.interpretResponse(serverResponse, serverRequest); + expect(bidResponses).to.have.lengthOf(1); + const response = bidResponses[0]; + expect(response.playerUrl).to.equal(serverResponse.body.ads[0].playerUrl); + }); + it('should not build bid response', function () { serverResponse = {}; const bidResponses = spec.interpretResponse(serverResponse, serverRequest); @@ -285,7 +366,7 @@ describe('RelaidoAdapter', function () { let userSyncs = spec.getUserSyncs({iframeEnabled: true}, [serverResponse]); expect(userSyncs).to.deep.equal([{ type: 'iframe', - url: serverResponse.body.syncUrl + url: serverResponse.body.syncUrl + '?uu=hogehoge' }]); }); @@ -293,7 +374,7 @@ describe('RelaidoAdapter', function () { let userSyncs = spec.getUserSyncs({iframeEnabled: true}, []); expect(userSyncs).to.deep.equal([{ type: 'iframe', - url: 'https://api.relaido.jp/tr/v1/prebid/sync.html' + url: 'https://api.relaido.jp/tr/v1/prebid/sync.html?uu=hogehoge' }]); }); @@ -302,7 +383,7 @@ describe('RelaidoAdapter', function () { let userSyncs = spec.getUserSyncs({iframeEnabled: true}, [serverResponse]); expect(userSyncs).to.deep.equal([{ type: 'iframe', - url: 'https://api.relaido.jp/tr/v1/prebid/sync.html' + url: 'https://api.relaido.jp/tr/v1/prebid/sync.html?uu=hogehoge' }]); }); @@ -313,19 +394,11 @@ describe('RelaidoAdapter', function () { }); describe('spec.onBidWon', function () { - let stub; - beforeEach(() => { - stub = sinon.stub(utils, 'triggerPixel'); - }); - afterEach(() => { - stub.restore(); - }); - it('Should create nurl pixel if bid nurl', function () { let bid = { bidder: bidRequest.bidder, - creativeId: serverResponse.body.creativeId, - cpm: serverResponse.body.price, + creativeId: serverResponse.body.ads[0].creativeId, + cpm: serverResponse.body.ads[0].price, params: [bidRequest.params], auctionId: bidRequest.auctionId, requestId: bidRequest.bidId, @@ -334,7 +407,7 @@ describe('RelaidoAdapter', function () { ref: window.location.href, } spec.onBidWon(bid); - const parser = utils.parseUrl(stub.getCall(0).args[0]); + const parser = utils.parseUrl(triggerPixelStub.getCall(0).args[0]); const query = parser.search; expect(parser.hostname).to.equal('api.relaido.jp'); expect(parser.pathname).to.equal('/tr/v1/prebid/win.gif'); @@ -350,14 +423,6 @@ describe('RelaidoAdapter', function () { }); describe('spec.onTimeout', function () { - let stub; - beforeEach(() => { - stub = sinon.stub(utils, 'triggerPixel'); - }); - afterEach(() => { - stub.restore(); - }); - it('Should create nurl pixel if bid nurl', function () { const data = [{ bidder: bidRequest.bidder, @@ -368,7 +433,7 @@ describe('RelaidoAdapter', function () { timeout: bidderRequest.timeout, }]; spec.onTimeout(data); - const parser = utils.parseUrl(stub.getCall(0).args[0]); + const parser = utils.parseUrl(triggerPixelStub.getCall(0).args[0]); const query = parser.search; expect(parser.hostname).to.equal('api.relaido.jp'); expect(parser.pathname).to.equal('/tr/v1/prebid/timeout.gif'); diff --git a/test/spec/modules/reloadBidAdapter_spec.js b/test/spec/modules/reloadBidAdapter_spec.js deleted file mode 100644 index b22dd9e7b92..00000000000 --- a/test/spec/modules/reloadBidAdapter_spec.js +++ /dev/null @@ -1,295 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/reloadBidAdapter.js'; - -let getParams = () => { - return JSON.parse(JSON.stringify({ - 'plcmID': 'placement_01', - 'partID': 'part00', - 'opdomID': 1, - 'bsrvID': 1, - 'type': 'pcm' - })); -}; - -let getBidderRequest = () => { - return JSON.parse(JSON.stringify({ - bidderCode: 'reload', - auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', - bidderRequestId: '7101db09af0db2', - start: new Date().getTime(), - bids: [{ - bidder: 'reload', - bidId: '84ab500420319d', - bidderRequestId: '7101db09af0db2', - auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', - params: getParams() - }] - })); -}; - -let getValidBidRequests = () => { - return JSON.parse(JSON.stringify([ - { - 'bidder': 'reload', - 'params': getParams(), - 'mediaTypes': { - 'banner': { - 'sizes': [[160, 600]] - } - }, - 'adUnitCode': '1b243858-3c53-43dc-9fdf-89f839ea4a0f', - 'transactionId': '8cbafa10-123d-4673-a1a5-04a1c7d62ded', - 'sizes': [[160, 600]], - 'bidId': '2236e11dc09931', - 'bidderRequestId': '1266bb886c2267', - 'auctionId': '4fb72c4d-94dc-4db1-8fac-3c2090ceeec0', - 'src': 'client', - 'bidRequestsCount': 1 - } - ])); -} - -let getExt1ServerResponse = () => { - return JSON.parse(JSON.stringify({ - 'pcmdata': { - 'thisVer': '100', - 'plcmSett': { - 'name': 'zz_test_mariano_adapter', - 'Version': '210', - 'lifeSpan': '100', - 'versionFolder': 'v4.14q', - 'versionFolderA': 'v4.14q', - 'versionFolderB': '', - 'stage': 'zz_test_mariano_adapter', - 'synchro': 1556916507000, - 'localCache': 'true', - 'testCase': 'A:00_B:100', - 'opdomain': '1', - 'checksum': '6378', - 'cpm': '0', - 'bstfct': '100', - 'totstop': 'false', - 'pcmurl': 'bidsrv01.reload.net' - }, - 'srvUrl': 'bidsrv01.reload.net', - 'instr': {'go': true, 'prc': 32, 'cur': 'USD'}, - 'statStr': 'eyN4aHYnQCk5OTotOC', - 'status': 'ok', - 'message': '', - 'log': '---- LOG ----' - }, - 'plcmID': 'zz_test_mariano_adapter', - 'partID': 'prx_part', - 'opdomID': '0', - 'bsrvID': 1, - 'adUnitCode': '1b243858-3c53-43dc-9fdf-89f839ea4a0f', - 'banner': {'w': 300, 'h': 250} - })); -} - -let getExt2ServerResponse = () => { - return JSON.parse(JSON.stringify({ - 'pcmdata': { - 'thisVer': '100', - 'plcmSett': { - 'name': 'placement_01', - 'Version': '210', - 'lifeSpan': '100', - 'versionFolder': 'v4.14q', - 'versionFolderA': 'v4.14q', - 'versionFolderB': '', - 'stage': 'placement_01', - 'synchro': 1556574760000, - 'localCache': 'true', - 'testCase': 'A:00_B:100', - 'opdomain': '1', - 'checksum': '6378', - 'cpm': '0', - 'bstfct': '100', - 'totstop': 'false', - 'pcmurl': 'bidsrv00.reload.net' - }, - 'srvUrl': 'bidsrv00.reload.net', - 'log': 'incomp_input_obj_version', - 'message': 'incomp_input_obj_version', - 'status': 'error' - }, - 'plcmID': 'placement_01', - 'partID': 'prx_part', - 'opdomID': '0', - 'bsrvID': 1, - 'adUnitCode': '1b243858-3c53-43dc-9fdf-89f839ea4a0f', - 'banner': {'w': 160, 'h': 600} - })); -} - -let getServerResponse = (pExt) => { - return JSON.parse(JSON.stringify({ - 'body': { - 'id': '2759340f70210d', - 'bidid': 'fbs-br-3mzdbycetjv8f8079', - 'seatbid': [ - { - 'bid': [ - { - 'id': 'fbs-br-stbd-bd-3mzdbycetjv8f807b', - 'price': 0, - 'nurl': '', - 'adm': '', - 'ext': pExt - } - ], - 'seat': 'fbs-br-stbd-3mzdbycetjv8f807a', - 'group': 0 - } - ] - }, - 'headers': {} - })); -} - -describe('ReloadAdapter', function () { - describe('isBidRequestValid', function () { - var bid = { - 'bidder': 'reload', - 'params': { - 'plcmID': 'placement_01', - 'partID': 'part00', - 'opdomID': 1, - 'bsrvID': 23, - 'type': 'pcm' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when bsrvID is not number', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'plcmID': 'placement_01', - 'partID': 'part00', - 'opdomID': 1, - 'bsrvID': 'abc' - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when bsrvID > 99', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'plcmID': 'placement_01', - 'partID': 'part00', - 'opdomID': 1, - 'bsrvID': 230 - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when bsrvID < 0', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'plcmID': 'placement_01', - 'partID': 'part00', - 'opdomID': 1, - 'bsrvID': -3, - 'type': 'pcm' - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'plcmID': 'placement_01' - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests()', function () { - let vRequests = spec.buildRequests(getValidBidRequests(), {}); - let vData = JSON.parse(vRequests[0].data); - - it('should send one requests', () => { - expect(vRequests.length).to.equal(1); - }); - - it('should send one requests, one impression', () => { - expect(vData.imp.length).to.equal(1); - }); - - it('should exists ext.type and ext.pcmdata', () => { - expect(vData.imp[0].banner).to.exist; - expect(vData.imp[0].banner.ext).to.exist; - expect(vData.imp[0].banner.ext.type).to.exist; - expect(vData.imp[0].banner.ext.pcmdata).to.exist; - expect(vData.imp[0].banner.ext.type).to.equal('pcm'); - }); - }); - - describe('interpretResponse()', function () { - it('Returns an empty array', () => { - let vData = spec.interpretResponse(getServerResponse(getExt2ServerResponse()), {}); - - expect(vData.length).to.equal(0); - }); - - it('Returns an array with one response', () => { - let vData = spec.interpretResponse(getServerResponse(getExt1ServerResponse()), {}); - expect(vData.length).to.equal(1); - }); - - it('required fileds', () => { - let vData = spec.interpretResponse(getServerResponse(getExt1ServerResponse()), {}); - expect(vData.length).to.equal(1); - expect(vData[0]).to.have.all.keys(['requestId', 'ad', 'cpm', 'width', 'height', 'creativeId', 'currency', 'ttl', 'netRevenue']); - }); - - it('CPM great than 0', () => { - let vData = spec.interpretResponse(getServerResponse(getExt1ServerResponse()), {}); - expect(vData[0].cpm).to.greaterThan(0); - }); - - it('instruction empty', () => { - let vResponse = Object.assign({}, getServerResponse(getExt1ServerResponse())); - vResponse.body.seatbid[0].bid[0].ext.pcmdata.instr = null; - let vData = spec.interpretResponse(vResponse, {}); - expect(vData.length).to.equal(0); - - vResponse = Object.assign({}, getServerResponse(getExt1ServerResponse())); - vResponse.body.seatbid[0].bid[0].ext.pcmdata.instr = undefined; - vData = spec.interpretResponse(vResponse, {}); - expect(vData.length).to.equal(0); - - vResponse = Object.assign({}, getServerResponse(getExt1ServerResponse())); - vResponse.body.seatbid[0].bid[0].ext.pcmdata.instr.go = undefined; - vData = spec.interpretResponse(vResponse, {}); - expect(vData.length).to.equal(0); - }); - - it('instruction with go = false', () => { - let vResponse = getServerResponse(getExt1ServerResponse()); - vResponse.body.seatbid[0].bid[0].ext.pcmdata.instr.go = false; - let vData = spec.interpretResponse(vResponse, {}); - expect(vData.length).to.equal(0); - }); - - it('incompatibility output object version (thisVer)', () => { - let vResponse = getServerResponse(getExt1ServerResponse()); - vResponse.body.seatbid[0].bid[0].ext.pcmdata.thisVer = '200'; - let vData = spec.interpretResponse(vResponse, {}); - expect(vData.length).to.equal(0); - }); - }); -}); diff --git a/test/spec/modules/smartrtbBidAdapter_spec.js b/test/spec/modules/resetdigitalBidAdapter_spec.js similarity index 85% rename from test/spec/modules/smartrtbBidAdapter_spec.js rename to test/spec/modules/resetdigitalBidAdapter_spec.js index a7f30bdec6e..34354ceeea8 100644 --- a/test/spec/modules/smartrtbBidAdapter_spec.js +++ b/test/spec/modules/resetdigitalBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { spec, _getPlatform } from 'modules/smartrtbBidAdapter.js' +import { spec, _getPlatform } from 'modules/resetdigitalBidAdapter.js' import { newBidder } from 'src/adapters/bidderFactory.js' const br = { @@ -13,8 +13,8 @@ const br = { crid: 'crid' }], pixels: [ - { type: 'image', url: 'https://smrtb.com/image' }, - { type: 'iframe', url: 'https://smrtb.com/iframe' } + { type: 'image', url: 'https://reset.com/image' }, + { type: 'iframe', url: 'https://reset.com/iframe' } ] } } @@ -30,13 +30,13 @@ const vr = { crid: 'video_crid' }], pixels: [ - { type: 'image', url: 'https://smrtb.com/image' }, - { type: 'iframe', url: 'https://smrtb.com/iframe' } + { type: 'image', url: 'https://reset.com/image' }, + { type: 'iframe', url: 'https://reset.com/iframe' } ] } } -describe('SmartRTBBidAdapter', function () { +describe('resetdigitalBidAdapter', function () { const adapter = newBidder(spec) let bannerRequest = { @@ -48,7 +48,7 @@ describe('SmartRTBBidAdapter', function () { } }, params: { - zoneId: 'N4zTDq3PPEHBIODv7cXK' + pubId: '12345' } } @@ -61,13 +61,13 @@ describe('SmartRTBBidAdapter', function () { } }, params: { - zoneId: 'CK6gUYp58EGopLJnUvM2' + pubId: 'CK6gUYp58EGopLJnUvM2' } } describe('codes', function () { - it('should return a bidder code of smartrtb', function () { - expect(spec.code).to.equal('smartrtb') + it('should return a bidder code of resetdigital', function () { + expect(spec.code).to.equal('resetdigital') }) }) @@ -76,7 +76,7 @@ describe('SmartRTBBidAdapter', function () { expect(spec.isBidRequestValid(bannerRequest)).to.be.true }) - it('should return false if any zone id and pub id missing', function () { + it('should return false if zone id and pub id missing', function () { expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { pubId: null, zoneId: null } }))).to.be.false }) }) diff --git a/test/spec/modules/resultsmediaBidAdapter_spec.js b/test/spec/modules/resultsmediaBidAdapter_spec.js deleted file mode 100644 index 0e2d4c0013a..00000000000 --- a/test/spec/modules/resultsmediaBidAdapter_spec.js +++ /dev/null @@ -1,574 +0,0 @@ -import {spec} from '../../../modules/resultsmediaBidAdapter.js'; -import * as utils from '../../../src/utils.js'; -import * as sinon from 'sinon'; - -var r1adapter = spec; - -describe('resultsmedia adapter tests', function () { - beforeEach(function() { - this.defaultBidderRequest = { - 'refererInfo': { - 'referer': 'Reference Page', - 'stack': [ - 'aodomain.dvl', - 'page.dvl' - ] - } - }; - }); - - describe('Verify 1.0 POST Banner Bid Request', function () { - it('buildRequests works', function () { - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - - expect(bidRequest.url).to.have.string('https://bid306.rtbsrv.com/bidder/?bid=3mhdom&zoneId=9999&hbv='); - expect(bidRequest.method).to.equal('POST'); - const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.site).to.not.equal(null); - expect(openrtbRequest.site.ref).to.equal('Reference Page'); - expect(openrtbRequest.device).to.not.equal(null); - expect(openrtbRequest.device.ua).to.equal(navigator.userAgent); - expect(openrtbRequest.device.dnt).to.equal(0); - expect(openrtbRequest.imp[0].banner).to.not.equal(null); - expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); - expect(openrtbRequest.imp[0].banner.format[0].h).to.equal(250); - expect(openrtbRequest.imp[0].ext.bidder.zoneId).to.equal(9999); - }); - - it('interpretResponse works', function() { - var bidList = { - 'body': [ - { - 'impid': 'div-gpt-ad-1438287399331-0', - 'w': 300, - 'h': 250, - 'adm': '
My Compelling Ad
', - 'price': 1, - 'crid': 'cr-cfy24' - } - ] - }; - - var bannerBids = r1adapter.interpretResponse(bidList); - - expect(bannerBids.length).to.equal(1); - const bid = bannerBids[0]; - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.creativeId).to.equal('cr-cfy24'); - expect(bid.currency).to.equal('USD'); - expect(bid.netRevenue).to.equal(true); - expect(bid.cpm).to.equal(1.0); - expect(bid.ttl).to.equal(350); - }); - }); - - describe('Verify POST Video Bid Request', function() { - it('buildRequests works', function () { - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480], - 'context': 'instream' - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', - 'sizes': [ - [300, 250] - ], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - - expect(bidRequest.url).to.have.string('https://bid306.rtbsrv.com/bidder/?bid=3mhdom&zoneId=9999&hbv='); - expect(bidRequest.method).to.equal('POST'); - const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.site).to.not.equal(null); - expect(openrtbRequest.device).to.not.equal(null); - expect(openrtbRequest.device.ua).to.equal(navigator.userAgent); - expect(openrtbRequest.device).to.have.property('dnt'); - expect(openrtbRequest.imp[0].video).to.not.equal(null); - expect(openrtbRequest.imp[0].video.w).to.equal(640); - expect(openrtbRequest.imp[0].video.h).to.equal(480); - expect(openrtbRequest.imp[0].video.mimes[0]).to.equal('video/mp4'); - expect(openrtbRequest.imp[0].video.protocols).to.eql([2, 3, 5, 6]); - expect(openrtbRequest.imp[0].video.startdelay).to.equal(0); - expect(openrtbRequest.imp[0].video.skip).to.equal(0); - expect(openrtbRequest.imp[0].video.playbackmethod).to.eql([1, 2, 3, 4]); - expect(openrtbRequest.imp[0].video.delivery[0]).to.equal(1); - expect(openrtbRequest.imp[0].video.api).to.eql([1, 2, 5]); - }); - - it('interpretResponse works', function() { - var bidList = { - 'body': [ - { - 'impid': 'div-gpt-ad-1438287399331-1', - 'price': 1, - 'adm': 'https://example.com/', - 'adomain': [ - 'test.com' - ], - 'cid': '467415', - 'crid': 'cr-vid', - 'w': 800, - 'h': 600 - } - ] - }; - - var videoBids = r1adapter.interpretResponse(bidList); - - expect(videoBids.length).to.equal(1); - const bid = videoBids[0]; - expect(bid.width).to.equal(800); - expect(bid.height).to.equal(600); - expect(bid.vastUrl).to.equal('https://example.com/'); - expect(bid.mediaType).to.equal('video'); - expect(bid.creativeId).to.equal('cr-vid'); - expect(bid.currency).to.equal('USD'); - expect(bid.netRevenue).to.equal(true); - expect(bid.cpm).to.equal(1.0); - expect(bid.ttl).to.equal(600); - }); - }); - - describe('misc buildRequests', function() { - it('should send GDPR Consent data to resultsmedia tag', function () { - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-3', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var consentString = 'testConsentString'; - var gdprBidderRequest = this.defaultBidderRequest; - gdprBidderRequest.gdprConsent = { - 'gdprApplies': true, - 'consentString': consentString - }; - - var bidRequest = r1adapter.buildRequests(bidRequestList, gdprBidderRequest); - - const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.user.ext.consent).to.equal(consentString); - expect(openrtbRequest.regs.ext.gdpr).to.equal(true); - }); - - it('prefer 2.0 sizes', function () { - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 600]] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - - const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); - expect(openrtbRequest.imp[0].banner.format[0].h).to.equal(600); - }); - - it('does not return request for invalid banner size configuration', function () { - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300]] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - expect(bidRequest.method).to.be.undefined; - }); - - it('does not return request for missing banner size configuration', function () { - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': {} - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - expect(bidRequest.method).to.be.undefined; - }); - - it('reject bad sizes', function () { - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': {'sizes': [['400', '500'], ['4n0', '5g0']]} - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.imp[0].banner.format.length).to.equal(1); - }); - - it('dnt is correctly set to 1', function () { - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 600]] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var dntStub = sinon.stub(utils, 'getDNT').returns(1); - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - - dntStub.restore(); - - const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.device.dnt).to.equal(1); - }); - - it('supports string video sizes', function () { - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'video': { - 'context': 'instream', - 'playerSize': ['600', '300'] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - - const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.imp[0].video.w).to.equal(600); - expect(openrtbRequest.imp[0].video.h).to.equal(300); - }); - - it('rejects bad video sizes', function () { - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'video': { - 'context': 'instream', - 'playerSize': ['badWidth', 'badHeight'] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - - const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.imp[0].video.w).to.be.undefined; - expect(openrtbRequest.imp[0].video.h).to.be.undefined; - }); - - it('supports missing video size', function () { - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - - const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.imp[0].video.w).to.be.undefined; - expect(openrtbRequest.imp[0].video.h).to.be.undefined; - }); - - it('should return empty site data when refererInfo is missing', function() { - delete this.defaultBidderRequest.refererInfo; - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - const openrtbRequest = JSON.parse(bidRequest.data); - - expect(openrtbRequest.site.domain).to.equal(''); - expect(openrtbRequest.site.page).to.equal(''); - expect(openrtbRequest.site.ref).to.equal(''); - }); - }); - - it('should return empty site.domain and site.page when refererInfo.stack is empty', function() { - this.defaultBidderRequest.refererInfo.stack = []; - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - const openrtbRequest = JSON.parse(bidRequest.data); - - expect(openrtbRequest.site.domain).to.equal(''); - expect(openrtbRequest.site.page).to.equal(''); - expect(openrtbRequest.site.ref).to.equal('Reference Page'); - }); - - it('should secure correctly', function() { - this.defaultBidderRequest.refererInfo.stack[0] = ['https://securesite.dvl']; - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - const openrtbRequest = JSON.parse(bidRequest.data); - - expect(openrtbRequest.imp[0].secure).to.equal(1); - }); - - it('should pass schain', function() { - var schain = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [{ - 'asi': 'indirectseller.com', - 'sid': '00001', - 'hp': 1 - }, { - 'asi': 'indirectseller-2.com', - 'sid': '00002', - 'hp': 1 - }] - }; - var bidRequestList = [ - { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead', - 'schain': schain - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - const openrtbRequest = JSON.parse(bidRequest.data); - - expect(openrtbRequest.source.ext.schain).to.deep.equal(schain); - }); - - describe('misc interpretResponse', function () { - it('No bid response', function() { - var noBidResponse = r1adapter.interpretResponse({ - 'body': '' - }); - expect(noBidResponse.length).to.equal(0); - }); - }); - - describe('isBidRequestValid', function () { - var bid = { - 'bidder': 'resultsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'adUnitCode': 'bannerDiv' - }; - - it('should return true when required params found', function () { - expect(r1adapter.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when placementId missing', function () { - delete bid.params.zoneId; - expect(r1adapter.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('getUserSyncs', function () { - it('returns an empty string', function () { - expect(r1adapter.getUserSyncs()).to.deep.equal([]); - }); - }); -}); diff --git a/test/spec/modules/revcontentBidAdapter_spec.js b/test/spec/modules/revcontentBidAdapter_spec.js index 0a0263837c6..022dd0e1aa9 100644 --- a/test/spec/modules/revcontentBidAdapter_spec.js +++ b/test/spec/modules/revcontentBidAdapter_spec.js @@ -93,6 +93,31 @@ describe('revcontent adapter', function () { assert.equal(request.device.ua, navigator.userAgent); }); + it('should send info about device and use getFloor', function () { + let validBidRequests = [{ + bidder: 'revcontent', + nativeParams: {}, + params: { + size: {width: 300, height: 250}, + apiKey: '8a33fa9cf220ae685dcc3544f847cdda858d3b1c', + userId: 673, + domain: 'test.com', + endpoint: 'trends-s0.revcontent.com', + bidfloor: 0.05 + } + }]; + validBidRequests[0].getFloor = () => { + return { + floor: 0.07, + currency: 'USD' + }; + }; + let request = spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}); + request = JSON.parse(request[0].data); + assert.equal(request.imp[0].bidfloor, 0.07); + assert.equal(request.device.ua, navigator.userAgent); + }); + it('should send info about the site and default bidfloor', function () { let validBidRequests = [{ bidder: 'revcontent', @@ -129,7 +154,6 @@ describe('revcontent adapter', function () { assert.deepEqual(request.site, { domain: 'test.com', page: 'page', - cat: ['IAB17'], publisher: {id: 673, domain: 'test.com'} }); }); @@ -140,7 +164,8 @@ describe('revcontent adapter', function () { let serverResponse = {}; let bidRequest = {}; - assert.ok(!spec.interpretResponse(serverResponse, bidRequest)); + let result = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(result.length, 0); }); const serverResponse = { @@ -153,7 +178,7 @@ describe('revcontent adapter', function () { id: '6bbe3eed-f443-4e2b-a8da-57fd6327b37d', impid: '1', price: 0.1, - adid: '4162547', + crid: '4162547', nurl: 'https://trends-s0.revcontent.com/push/track/?p=${AUCTION_PRICE}&d=nTCdHIfsgKOLFuV7DS1LF%2FnTk5HiFduGU65BgKgB%2BvKyG9YV7ceQWN76HMbBE0C6gwQeXUjravv3Hq5x9TT8CM6r2oUNgkGC9mhgv2yroTH9i3cSoH%2BilxyY19fMXFirtBz%2BF%2FEXKi4bsNh%2BDMPfj0L4elo%2FJEZmx4nslvOneJJjsFjJJtUJc%2F3UPivOisSCa%2B36mAgFQqt%2FSWBriYB%2BVAufz70LaGspF6T6jDzuIyVFJUpLhZVDtLRSJEzh7Lyzzw1FmYarp%2FPg0gZDY48aDdjw5A3Tlj%2Bap0cPHLDprNOyF0dmHDn%2FOVJEDRTWvrQ2JNK1t%2Fg1bGHIih0ec6XBVIBNurqRpLFBuUY6LgXCt0wRZWTByTEZ8AEv8IoYVILJAL%2BXL%2F9IyS4eTcdOUfn5X7gT8QBghCrAFrsCg8ZXKgWddTEXbpN1lU%2FzHdI5eSHkxkJ6WcYxSkY9PyripaIbmKiyb98LQMgTD%2B20RJO5dAmXTQTAcauw6IUPTjgSPEU%2Bd6L5Txd3CM00Hbd%2Bw1bREIQcpKEmlMwrRSwe4bu1BCjlh5A9gvU9Xc2sf7ekS3qPPmtp059r5IfzdNFQJB5aH9HqeDEU%2FxbMHx4ggMgojLBBL1fKrCKLAteEDQxd7PVmFJv7GHU2733vt5TnjKiEhqxHVFyi%2B0MIYMGIziM5HfUqfq3KUf%2F%2FeiCtJKXjg7FS6hOambdimSt7BdGDIZq9QECWdXsXcQqqVLwli27HYDMFVU3TWWRyjkjbhnQID9gQJlcpwIi87jVAODb6qP%2FKGQ%3D%3D', adm: '{"ver":"1.1","assets":[{"id":3,"required":1,"img":{"url":"//img.revcontent.com/?url=https://revcontent-p0.s3.amazonaws.com/content/images/15761052960288727821.jpg&static=true"}},{"id":0,"required":1,"title":{"text":"Do You Eat Any of These Craving-trigger Foods?"}},{"id":5,"required":1,"data":{"value":""}}],"link":{"url":"https://trends-s0.revcontent.com/click.php?d=A7EVbNYBVyonty19Ak08zCr9J54qg%2Bmduq6p0Zyn5%2F%2Bapm4deUo9VAXmOGEIbUBf6i7m3%2F%2FWJm%2FzTha8SJ%2Br9MZL9jhhUxDeiKb6aRY1biLrvr6tFUd1phvtKqVmPd76l9VBLFMxS1brSzKjRCJlIGmyGJg7ueFvxpE9X%2BpHmdbE2uqUdRC49ENO3XZyHCCKMAZ8XD29fasX9Kli9mKpZTqw8vayFlXbVYSUwB8wfSwCt1sIUrt0aICYc0jcyWU3785GTS1xXzQj%2FIVszFYYrdTWd%2BDijjNZtFny0OomPHp8lRy5VcQVCuLpw0Fks4myvsE38XcNvs4wO3tWTNrI%2BMqcW1%2BD2OnMSq5nN5FCbmi2ly%2F1LbN9fibaFvW%2FQbzQhN9ZsAwmhm409UTtdmSA6hd96vDxDWLeUJhVO3UQyI0yq2TtVnB9tEICD8mZNWwYehOab%2BQ1EWmTerF6ZCDx8RyZus1UrsDfRwvTCyUjCmkZhmeo4QVJkpPy6QobCsngSaxkkKhH%2Fb7coZyBXXEt3ORoYBLUbfRO6nR8GdIt8413vrYr4gTAroh46VcWK0ls0gFNe2u3%2FqP%2By1yLKbzDVaR%2Fa02G%2Biiqbw86sCYfsy7qK9atyjNTm8RkH6JLESUzxc6IEazu4iwHKGnu5phTacmseXCi8y9Y5AdBZn8VnLP%2F2a%2FyAqq93xEH%2BIrkAdhGRY1tY39rBYAtvH%2FVyNFZcong%2FutUMYbp0WhDNyfl6iWxmpE28Cx9KDcqXss0NIwQm0AWeu8ogJCIG3faAkm5PdFsUdf2X9h3HuFDbnbvnXW27ml6z9GykEzv%2F8aSZlMZ"}}' } @@ -310,7 +335,7 @@ describe('revcontent adapter', function () { } }; let bidRequest = { - data: {}, + data: '{}', bids: [{bidId: 'bidId1'}] }; const result = spec.interpretResponse(serverResponse, bidRequest)[0]; diff --git a/test/spec/modules/rhythmoneBidAdapter_spec.js b/test/spec/modules/rhythmoneBidAdapter_spec.js index 93735c019e1..e0e58fc89cd 100644 --- a/test/spec/modules/rhythmoneBidAdapter_spec.js +++ b/test/spec/modules/rhythmoneBidAdapter_spec.js @@ -444,7 +444,7 @@ describe('rhythmone adapter tests', function () { expect(openrtbRequest.device.dnt).to.equal(1); }); - it('sets floor', function () { + it('sets floor to zero', function () { var bidRequestList = [ { 'bidder': 'rhythmone', @@ -469,7 +469,7 @@ describe('rhythmone adapter tests', function () { var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.imp[0].bidfloor).to.equal(100.0); + expect(openrtbRequest.imp[0].bidfloor).to.equal(0); }); it('supports string video sizes', function () { diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 72410b71fb2..9e9366072be 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -149,8 +149,8 @@ describe('Richaudience adapter tests', function () { netRevenue: true, currency: 'USD', ttl: 300, - dealId: 'dealId' - + dealId: 'dealId', + adomain: 'richaudience.com' } }; @@ -165,7 +165,8 @@ describe('Richaudience adapter tests', function () { currency: 'USD', ttl: 300, vastXML: '', - dealId: 'dealId' + dealId: 'dealId', + adomain: 'richaudience.com' } }; @@ -600,6 +601,7 @@ describe('Richaudience adapter tests', function () { expect(bid.currency).to.equal('USD'); expect(bid.ttl).to.equal(300); expect(bid.dealId).to.equal('dealId'); + expect(bid.meta).to.equal('richaudience.com'); }); it('no banner media response inestream', function () { @@ -628,6 +630,7 @@ describe('Richaudience adapter tests', function () { expect(bid.currency).to.equal('USD'); expect(bid.ttl).to.equal(300); expect(bid.dealId).to.equal('dealId'); + expect(bid.meta).to.equal('richaudience.com'); }); it('no banner media response outstream', function () { @@ -788,6 +791,46 @@ describe('Richaudience adapter tests', function () { })).to.equal(true); }); + it('should pass schain', function() { + let schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'richaudience.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'richaudience-2.com', + 'sid': '00002', + 'hp': 1 + }] + } + + DEFAULT_PARAMS_NEW_SIZES[0].schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'richaudience.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'richaudience-2.com', + 'sid': '00002', + 'hp': 1 + }] + } + + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('schain').to.deep.equal(schain); + }) + describe('userSync', function () { it('Verifies user syncs iframe include', function () { config.setConfig({ @@ -902,7 +945,7 @@ describe('Richaudience adapter tests', function () { }, [], { consentString: null, referer: 'http://domain.com', - gdprApplies: true + gdprApplies: false }) expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('image'); @@ -939,7 +982,7 @@ describe('Richaudience adapter tests', function () { }, [], { consentString: null, referer: 'http://domain.com', - gdprApplies: true + gdprApplies: false }) expect(syncs).to.have.lengthOf(0); }); diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index b3257cbda9d..d5504b1e57e 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/riseBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; import { VIDEO } from '../../../src/mediaTypes.js'; +import * as utils from 'src/utils.js'; const ENDPOINT = 'https://hb.yellowblue.io/hb'; const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-test'; @@ -74,30 +75,21 @@ describe('riseAdapter', function () { const bidderRequest = { bidderCode: 'rise', } + const placementId = '12345678'; - const customSessionId = '12345678'; - - it('sends bid request to ENDPOINT via GET', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('GET'); - } - }); - - it('sends the is_wrapper query param', function () { - bidRequests[0].params.isWrapper = true; + it('sends the placementId as a query param', function () { + bidRequests[0].params.placementId = placementId; const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { - expect(request.data.is_wrapper).to.equal(true); + expect(request.data.placement_id).to.equal(placementId); } }); - it('sends the custom session id as a query param', function () { - bidRequests[0].params.sessionId = customSessionId; + it('sends bid request to ENDPOINT via GET', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { - expect(request.data.session_id).to.equal(customSessionId); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); } }); @@ -265,9 +257,37 @@ describe('riseAdapter', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,,,,'); + expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); } }); + + it('should set floor_price to getFloor.floor value if it is greater than params.floorPrice', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 3.32 + } + } + bid.params.floorPrice = 0.64; + const request = spec.buildRequests([bid], bidderRequest)[0]; + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('floor_price', 3.32); + }); + + it('should set floor_price to params.floorPrice value if it is greater than getFloor.floor', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 0.8 + } + } + bid.params.floorPrice = 1.5; + const request = spec.buildRequests([bid], bidderRequest)[0]; + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('floor_price', 1.5); + }); }); describe('interpretResponse', function () { @@ -278,7 +298,9 @@ describe('riseAdapter', function () { height: 480, requestId: '21e12606d47ba7', netRevenue: true, - currency: 'USD' + currency: 'USD', + adomain: ['abc.com'], + nurl: 'http://example.com/win/1234', }; it('should get correct bid response', function () { @@ -293,7 +315,11 @@ describe('riseAdapter', function () { netRevenue: true, ttl: TTL, vastXml: '', - mediaType: VIDEO + mediaType: VIDEO, + nurl: 'http://example.com/win/1234', + meta: { + advertiserDomains: ['abc.com'] + } } ]; const result = spec.interpretResponse({ body: response }); @@ -378,4 +404,28 @@ describe('riseAdapter', function () { expect(syncs).to.deep.equal([]); }); }) + + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should trigger pixel if bid nurl', function() { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'nurl': 'http://example.com/win/1234', + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + spec.onBidWon(bid); + expect(utils.triggerPixel.callCount).to.equal(1) + }) + }) }); diff --git a/test/spec/modules/rtbdemandBidAdapter_spec.js b/test/spec/modules/rtbdemandBidAdapter_spec.js deleted file mode 100644 index be9872ec01b..00000000000 --- a/test/spec/modules/rtbdemandBidAdapter_spec.js +++ /dev/null @@ -1,174 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/rtbdemandBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -describe('rtbdemandAdapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'rtbdemand', - 'params': { - 'zoneid': '37', - 'floor': '0.05', - 'server': 'bidding.rtbdemand.com', - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'zoneid': '37', - }; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'zoneid': 0, - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let bidderRequest = { - bidderCode: 'rtbdemand', - auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', - bidderRequestId: '178e34bad3658f', - bids: [ - { - bidder: 'rtbdemand', - params: { - zoneid: '37', - floor: '0.05', - server: 'bidding.rtbdemand.com', - }, - placementCode: '/19968336/header-bid-tag-0', - sizes: [[300, 250], [320, 50]], - bidId: '2ffb201a808da7', - bidderRequestId: '178e34bad3658f', - auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', - transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' - }, - { - bidder: 'rtbdemand', - params: { - zoneid: '37', - floor: '0.05', - server: 'bidding.rtbdemand.com', - }, - placementCode: '/19968336/header-bid-tag-0', - sizes: [[728, 90], [320, 50]], - bidId: '2ffb201a808da7', - bidderRequestId: '178e34bad3658f', - auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', - transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' - } - ], - start: 1472239426002, - auctionStart: 1472239426000, - timeout: 5000 - }; - - it('should add source and verison to the tag', function () { - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const payload = request.data; - expect(payload.from).to.exist; - expect(payload.v).to.exist; - expect(payload.request_id).to.exist; - expect(payload.imp_id).to.exist; - expect(payload.aff).to.exist; - expect(payload.bid_floor).to.exist; - expect(payload.charset).to.exist; - expect(payload.site_domain).to.exist; - expect(payload.site_page).to.exist; - expect(payload.subid).to.exist; - expect(payload.flashver).to.exist; - expect(payload.tmax).to.exist; - }); - - it('sends bid request to ENDPOINT via GET', function () { - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.url).to.equal('https://bidding.rtbdemand.com/hb'); - expect(request.method).to.equal('GET'); - }); - }) - - describe('interpretResponse', function () { - let response = { - 'id': '543210', - 'seatbid': [ { - 'bid': [ { - 'id': '1111111', - 'impid': 'bidId-123456-1', - 'w': 728, - 'h': 90, - 'price': 0.09, - 'adm': '', - } ], - } ] - }; - - it('should get correct bid response', function () { - let expectedResponse = [ - { - requestId: 'bidId-123456-1', - creativeId: 'bidId-123456-1', - cpm: 0.09, - width: 728, - height: 90, - ad: '', - netRevenue: true, - currency: 'USD', - ttl: 360, - } - ]; - - let result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); - }); - - it('handles nobid responses', function () { - let response = { - 'id': '543210', - 'seatbid': [ ] - }; - - let result = spec.interpretResponse({ body: response }); - expect(result.length).to.equal(0); - }); - }); - - describe('user sync', function () { - const syncUrl = 'https://bidding.rtbdemand.com/delivery/matches.php?type=iframe'; - - it('should register the sync iframe', function () { - expect(spec.getUserSyncs({})).to.be.undefined; - expect(spec.getUserSyncs({iframeEnabled: false})).to.be.undefined; - const options = spec.getUserSyncs({iframeEnabled: true}); - expect(options).to.not.be.undefined; - expect(options).to.have.lengthOf(1); - expect(options[0].type).to.equal('iframe'); - expect(options[0].url).to.equal(syncUrl); - }); - }); -}); diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index d6bee26d73b..f4bcb48474a 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -68,6 +68,7 @@ describe('RTBHouseAdapter', () => { 'params': { 'publisherId': 'PREBID_TEST', 'region': 'prebid-eu', + 'channel': 'Partner_Site - news', 'test': 1 }, 'adUnitCode': 'adunit-code', @@ -101,6 +102,25 @@ describe('RTBHouseAdapter', () => { expect(JSON.parse(builtTestRequest).test).to.equal(1); }); + it('should build channel param into request.site', () => { + let builtTestRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(JSON.parse(builtTestRequest).site.channel).to.equal('Partner_Site - news'); + }) + + it('should not build channel param into request.site if no value is passed', () => { + let bidRequest = Object.assign([], bidRequests); + bidRequest[0].params.channel = undefined; + let builtTestRequest = spec.buildRequests(bidRequest, bidderRequest).data; + expect(JSON.parse(builtTestRequest).site.channel).to.be.undefined + }) + + it('should cap the request.site.channel length to 50', () => { + let bidRequest = Object.assign([], bidRequests); + bidRequest[0].params.channel = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent scelerisque ipsum eu purus lobortis iaculis.'; + let builtTestRequest = spec.buildRequests(bidRequest, bidderRequest).data; + expect(JSON.parse(builtTestRequest).site.channel.length).to.equal(50) + }) + it('should build valid OpenRTB banner object', () => { const request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); const imp = request.imp[0]; diff --git a/test/spec/modules/rtbsapeBidAdapter_spec.js b/test/spec/modules/rtbsapeBidAdapter_spec.js index 4c3814385b3..eea9e51b1a9 100644 --- a/test/spec/modules/rtbsapeBidAdapter_spec.js +++ b/test/spec/modules/rtbsapeBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; import {spec} from 'modules/rtbsapeBidAdapter.js'; +import 'src/prebid.js'; import * as utils from 'src/utils.js'; import {executeRenderer, Renderer} from 'src/Renderer.js'; @@ -46,7 +47,10 @@ describe('rtbsapeBidAdapterTests', function () { width: 240, height: 400, netRevenue: true, - ad: 'Ad html' + ad: 'Ad html', + meta: { + advertiserDomains: ['rtb.sape.ru'] + } }] } }; @@ -78,6 +82,7 @@ describe('rtbsapeBidAdapterTests', function () { netRevenue: true, vastUrl: 'https://cdn-rtb.sape.ru/vast/4321.xml', meta: { + advertiserDomains: ['rtb.sape.ru'], mediaType: 'video' } }] @@ -125,6 +130,7 @@ describe('rtbsapeBidAdapterTests', function () { }; executeRenderer(bid.renderer, bid); + bid.renderer.callback(); expect(spy).to.not.equal(false); expect(spy.called).to.be.true; @@ -136,6 +142,43 @@ describe('rtbsapeBidAdapterTests', function () { expect(spyCall.returnValue[3]).to.be.equal(false); }); }); + + it('skip adomain', function () { + let serverResponse = { + body: { + bids: [{ + requestId: 'bid1234', + cpm: 2.21, + currency: 'RUB', + width: 240, + height: 400, + netRevenue: true, + ad: 'Ad html 1' + }, { + requestId: 'bid1235', + cpm: 2.23, + currency: 'RUB', + width: 300, + height: 250, + netRevenue: true, + ad: 'Ad html 2', + meta: { + advertiserDomains: ['rtb.sape.ru'] + } + }] + } + }; + let bids = spec.interpretResponse(serverResponse, {data: {bids: [{mediaTypes: {banner: true}}]}}); + expect(bids).to.have.lengthOf(1); + let bid = bids[0]; + expect(bid.cpm).to.equal(2.23); + expect(bid.currency).to.equal('RUB'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.netRevenue).to.equal(true); + expect(bid.requestId).to.equal('bid1235'); + expect(bid.ad).to.equal('Ad html 2'); + }); }); it('getUserSyncs', function () { diff --git a/test/spec/modules/rtbsolutionsBidAdapter_spec.js b/test/spec/modules/rtbsolutionsBidAdapter_spec.js deleted file mode 100644 index c47b086fe50..00000000000 --- a/test/spec/modules/rtbsolutionsBidAdapter_spec.js +++ /dev/null @@ -1,62 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/rtbsolutionsBidAdapter.js'; - -describe('rtbsolutionsBidAdapterTests', function () { - it('validate_pub_params_1', function () { - expect(spec.isBidRequestValid({ - bidder: 'rtbsolutions', - params: { - blockId: 777 - } - })).to.equal(true); - }); - it('validate_pub_params_2', function () { - expect(spec.isBidRequestValid({ - bidder: 'rtbsolutions', - params: { - s1: 'test' - } - })).to.equal(false); - }); - it('validate_generated_params', function () { - let bidderRequest = { - bids: [], - refererInfo: { - referer: '' - } - }; - bidderRequest.bids.push({ - bidId: 'bid1234', - bidder: 'rtbsolutions', - params: {blockId: 777}, - sizes: [[240, 400]] - }); - let request = spec.buildRequests(true, bidderRequest); - let req_data = request.data[0]; - expect(req_data.bid_id).to.equal('bid1234'); - }); - it('validate_response_params', function () { - let serverResponse = { - body: [{ - ad: 'Ad html', - bid_id: 'bid1234', - cpm: 1, - creative_id: 1, - height: 480, - nurl: 'http://test.test', - width: 640, - currency: 'USD', - }] - }; - let bids = spec.interpretResponse(serverResponse); - expect(bids).to.have.lengthOf(1); - let bid = bids[0]; - expect(bid.cpm).to.equal(1); - expect(bid.currency).to.equal('USD'); - expect(bid.width).to.equal(640); - expect(bid.height).to.equal(480); - expect(bid.netRevenue).to.equal(true); - expect(bid.requestId).to.equal('bid1234'); - expect(bid.ad).to.equal('Ad html'); - }); -}); diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index df8bf03b56a..584fe718c36 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -168,8 +168,8 @@ const floorMinRequest = { 'zoneId': '335918', 'userId': '12346', 'keywords': ['a', 'b', 'c'], - 'inventory': {'rating': '4-star', 'prodtype': 'tech'}, - 'visitor': {'ucat': 'new', 'lastsearch': 'iphone'}, + 'inventory': { 'rating': '4-star', 'prodtype': 'tech' }, + 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, 'position': 'atf' }, 'mediaTypes': { @@ -195,24 +195,24 @@ const MOCK = { 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', 'timestamp': 1519767010567, 'auctionStatus': 'inProgress', - 'adUnits': [ { + 'adUnits': [{ 'code': '/19968336/header-bid-tag1', 'sizes': [[640, 480]], - 'bids': [ { + 'bids': [{ 'bidder': 'rubicon', 'params': { 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 } - } ], + }], 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' } ], 'adUnitCodes': ['/19968336/header-bid-tag1'], - 'bidderRequests': [ { + 'bidderRequests': [{ 'bidderCode': 'rubicon', 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', 'bidderRequestId': '1be65d7958826a', - 'bids': [ { + 'bids': [{ 'bidder': 'rubicon', 'params': { 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 @@ -246,7 +246,7 @@ const MOCK = { } }, BID_REQUESTED: { - 'bidder': 'rubicon', + 'bidderCode': 'rubicon', 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', 'bidderRequestId': '1be65d7958826a', 'bids': [ @@ -259,7 +259,7 @@ const MOCK = { 'userId': '12346', 'keywords': ['a', 'b', 'c'], 'inventory': 'test', - 'visitor': {'ucat': 'new', 'lastsearch': 'iphone'}, + 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, 'position': 'btf', 'video': { 'language': 'en', @@ -274,7 +274,12 @@ const MOCK = { } } }, - 'mediaType': 'video', + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [640, 480] + } + }, 'adUnitCode': '/19968336/header-bid-tag-0', 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', 'sizes': [[640, 480]], @@ -291,8 +296,8 @@ const MOCK = { 'zoneId': '335918', 'userId': '12346', 'keywords': ['a', 'b', 'c'], - 'inventory': {'rating': '4-star', 'prodtype': 'tech'}, - 'visitor': {'ucat': 'new', 'lastsearch': 'iphone'}, + 'inventory': { 'rating': '4-star', 'prodtype': 'tech' }, + 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, 'position': 'atf' }, 'mediaTypes': { @@ -362,7 +367,6 @@ const STUBBED_UUID = '12345678-1234-1234-1234-123456789abc'; const ANALYTICS_MESSAGE = { 'channel': 'web', - 'eventTimeMillis': 1519767013781, 'integration': 'pbjs', 'version': '$prebid.version$', 'referrerUri': 'http://www.test.com/page.html', @@ -371,9 +375,19 @@ const ANALYTICS_MESSAGE = { 'id': STUBBED_UUID, 'start': 1519767013781 }, + 'timestamps': { + 'auctionEnded': 1519767013781, + 'eventTime': 1519767013781, + 'prebidLoaded': rubiconAnalyticsAdapter.MODULE_INITIALIZED_TIME + }, + 'trigger': 'allBidWons', 'referrerHostname': 'www.test.com', 'auctions': [ { + + 'auctionEnd': 1519767013781, + 'auctionStart': 1519767010567, + 'bidderOrder': ['rubicon'], 'requestId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', 'clientTimeoutMillis': 3000, 'serverTimeoutMillis': 1000, @@ -645,10 +659,10 @@ describe('rubicon analytics adapter', function () { expect(utils.logError.called).to.equal(true); }); - describe('config subscribe', function() { + describe('config subscribe', function () { it('should update the pvid if user asks', function () { expect(utils.generateUUID.called).to.equal(false); - config.setConfig({rubicon: {updatePageView: true}}); + config.setConfig({ rubicon: { updatePageView: true } }); expect(utils.generateUUID.called).to.equal(true); }); it('should merge in and preserve older set configs', function () { @@ -843,6 +857,68 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(ANALYTICS_MESSAGE); }); + it('should pass along bidderOrder correctly', function () { + const appnexusBid = utils.deepClone(MOCK.BID_REQUESTED); + appnexusBid.bidderCode = 'appnexus'; + const pubmaticBid = utils.deepClone(MOCK.BID_REQUESTED); + pubmaticBid.bidderCode = 'pubmatic'; + const indexBid = utils.deepClone(MOCK.BID_REQUESTED); + indexBid.bidderCode = 'ix'; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, pubmaticBid); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_REQUESTED, indexBid); + events.emit(BID_REQUESTED, appnexusBid); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + clock.tick(SEND_TIMEOUT + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.auctions[0].bidderOrder).to.deep.equal([ + 'pubmatic', + 'rubicon', + 'ix', + 'appnexus' + ]); + }); + + it('should pass along user ids', function () { + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.bidderRequests[0].bids[0].userId = { + criteoId: 'sadfe4334', + lotamePanoramaId: 'asdf3gf4eg', + pubcid: 'dsfa4545-svgdfs5', + sharedId: { id1: 'asdf', id2: 'sadf4344' } + }; + + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + + let message = JSON.parse(request.requestBody); + validate(message); + + expect(message.auctions[0].user).to.deep.equal({ + ids: [ + { provider: 'criteoId', 'hasId': true }, + { provider: 'lotamePanoramaId', 'hasId': true }, + { provider: 'pubcid', 'hasId': true }, + { provider: 'sharedId', 'hasId': true }, + ] + }); + }); + it('should handle bidResponse dimensions correctly', function () { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); @@ -941,6 +1017,58 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; }); + it('should NOT pass along adomians with other edge cases', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // should filter out non string values and pass valid ones + let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); + bidResponse1.meta = { + advertiserDomains: [123, 'prebid.org', false, true, [], 'magnite.com', {}] + } + + // array of arrays (as seen when passed by kargo bid adapter) + let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); + bidResponse2.meta = { + advertiserDomains: [['prebid.org']] + } + + events.emit(BID_RESPONSE, bidResponse1); + events.emit(BID_RESPONSE, bidResponse2); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com']); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; + }); + + it('should not pass empty adServerTargeting values', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + const mockTargeting = utils.deepClone(MOCK.SET_TARGETING); + mockTargeting['/19968336/header-bid-tag-0'].hb_test = ''; + mockTargeting['/19968336/header-bid-tag1'].hb_test = 'NOT_EMPTY'; + events.emit(SET_TARGETING, mockTargeting); + + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].adserverTargeting.hb_test).to.be.undefined; + expect(message.auctions[0].adUnits[1].adserverTargeting.hb_test).to.equal('NOT_EMPTY'); + }); + function performFloorAuction(provider) { let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); auctionInit.bidderRequests[0].bids[0].floorData = { @@ -1138,7 +1266,7 @@ describe('rubicon analytics adapter', function () { describe('with session handling', function () { const expectedPvid = STUBBED_UUID.slice(0, 8); beforeEach(function () { - config.setConfig({rubicon: {updatePageView: true}}); + config.setConfig({ rubicon: { updatePageView: true } }); }); it('should not log any session data if local storage is not enabled', function () { @@ -1162,12 +1290,14 @@ describe('rubicon analytics adapter', function () { }); it('should should pass along custom rubicon kv and pvid when defined', function () { - config.setConfig({rubicon: { - fpkvs: { - source: 'fb', - link: 'email' + config.setConfig({ + rubicon: { + fpkvs: { + source: 'fb', + link: 'email' + } } - }}); + }); performStandardAuction(); expect(server.requests.length).to.equal(1); let request = server.requests[0]; @@ -1177,22 +1307,24 @@ describe('rubicon analytics adapter', function () { let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); expectedMessage.fpkvs = [ - {key: 'source', value: 'fb'}, - {key: 'link', value: 'email'} + { key: 'source', value: 'fb' }, + { key: 'link', value: 'email' } ] expect(message).to.deep.equal(expectedMessage); }); it('should convert kvs to strings before sending', function () { - config.setConfig({rubicon: { - fpkvs: { - number: 24, - boolean: false, - string: 'hello', - array: ['one', 2, 'three'], - object: {one: 'two'} + config.setConfig({ + rubicon: { + fpkvs: { + number: 24, + boolean: false, + string: 'hello', + array: ['one', 2, 'three'], + object: { one: 'two' } + } } - }}); + }); performStandardAuction(); expect(server.requests.length).to.equal(1); let request = server.requests[0]; @@ -1202,24 +1334,26 @@ describe('rubicon analytics adapter', function () { let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); expectedMessage.fpkvs = [ - {key: 'number', value: '24'}, - {key: 'boolean', value: 'false'}, - {key: 'string', value: 'hello'}, - {key: 'array', value: 'one,2,three'}, - {key: 'object', value: '[object Object]'} + { key: 'number', value: '24' }, + { key: 'boolean', value: 'false' }, + { key: 'string', value: 'hello' }, + { key: 'array', value: 'one,2,three' }, + { key: 'object', value: '[object Object]' } ] expect(message).to.deep.equal(expectedMessage); }); it('should use the query utm param rubicon kv value and pass updated kv and pvid when defined', function () { - sandbox.stub(utils, 'getWindowLocation').returns({'search': '?utm_source=other', 'pbjs_debug': 'true'}); + sandbox.stub(utils, 'getWindowLocation').returns({ 'search': '?utm_source=other', 'pbjs_debug': 'true' }); - config.setConfig({rubicon: { - fpkvs: { - source: 'fb', - link: 'email' + config.setConfig({ + rubicon: { + fpkvs: { + source: 'fb', + link: 'email' + } } - }}); + }); performStandardAuction(); expect(server.requests.length).to.equal(1); let request = server.requests[0]; @@ -1229,8 +1363,8 @@ describe('rubicon analytics adapter', function () { let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); expectedMessage.fpkvs = [ - {key: 'source', value: 'other'}, - {key: 'link', value: 'email'} + { key: 'source', value: 'other' }, + { key: 'link', value: 'email' } ] message.fpkvs.sort((left, right) => left.key < right.key); @@ -1250,11 +1384,13 @@ describe('rubicon analytics adapter', function () { }; getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - config.setConfig({rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! + config.setConfig({ + rubicon: { + fpkvs: { + link: 'email' // should merge this with what is in the localStorage! + } } - }}); + }); performStandardAuction(); expect(server.requests.length).to.equal(1); let request = server.requests[0]; @@ -1269,8 +1405,8 @@ describe('rubicon analytics adapter', function () { pvid: expectedPvid } expectedMessage.fpkvs = [ - {key: 'source', value: 'tw'}, - {key: 'link', value: 'email'} + { key: 'source', value: 'tw' }, + { key: 'link', value: 'email' } ] expect(message).to.deep.equal(expectedMessage); @@ -1292,7 +1428,7 @@ describe('rubicon analytics adapter', function () { }); it('should overwrite matching localstorge value and use its remaining values', function () { - sandbox.stub(utils, 'getWindowLocation').returns({'search': '?utm_source=fb&utm_click=dog'}); + sandbox.stub(utils, 'getWindowLocation').returns({ 'search': '?utm_source=fb&utm_click=dog' }); // set some localStorage let inputlocalStorage = { @@ -1304,11 +1440,13 @@ describe('rubicon analytics adapter', function () { }; getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - config.setConfig({rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! + config.setConfig({ + rubicon: { + fpkvs: { + link: 'email' // should merge this with what is in the localStorage! + } } - }}); + }); performStandardAuction(); expect(server.requests.length).to.equal(1); let request = server.requests[0]; @@ -1323,9 +1461,9 @@ describe('rubicon analytics adapter', function () { pvid: expectedPvid } expectedMessage.fpkvs = [ - {key: 'source', value: 'fb'}, - {key: 'link', value: 'email'}, - {key: 'click', value: 'dog'} + { key: 'source', value: 'fb' }, + { key: 'link', value: 'email' }, + { key: 'click', value: 'dog' } ] message.fpkvs.sort((left, right) => left.key < right.key); @@ -1361,11 +1499,13 @@ describe('rubicon analytics adapter', function () { }; getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - config.setConfig({rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! + config.setConfig({ + rubicon: { + fpkvs: { + link: 'email' // should merge this with what is in the localStorage! + } } - }}); + }); performStandardAuction(); expect(server.requests.length).to.equal(1); @@ -1379,7 +1519,7 @@ describe('rubicon analytics adapter', function () { // the saved fpkvs should have been thrown out since session expired expectedMessage.fpkvs = [ - {key: 'link', value: 'email'} + { key: 'link', value: 'email' } ] expect(message).to.deep.equal(expectedMessage); @@ -1411,11 +1551,13 @@ describe('rubicon analytics adapter', function () { }; getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - config.setConfig({rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! + config.setConfig({ + rubicon: { + fpkvs: { + link: 'email' // should merge this with what is in the localStorage! + } } - }}); + }); performStandardAuction(); expect(server.requests.length).to.equal(1); @@ -1429,7 +1571,7 @@ describe('rubicon analytics adapter', function () { // the saved fpkvs should have been thrown out since session expired expectedMessage.fpkvs = [ - {key: 'link', value: 'email'} + { key: 'link', value: 'email' } ] expect(message).to.deep.equal(expectedMessage); @@ -1455,7 +1597,7 @@ describe('rubicon analytics adapter', function () { let gptSlotRenderEnded0, gptSlotRenderEnded1; beforeEach(function () { mockGpt.enable(); - gptSlot0 = mockGpt.makeSlot({code: '/19968336/header-bid-tag-0'}); + gptSlot0 = mockGpt.makeSlot({ code: '/19968336/header-bid-tag-0' }); gptSlotRenderEnded0 = { eventName: 'slotRenderEnded', params: { @@ -1467,7 +1609,7 @@ describe('rubicon analytics adapter', function () { } }; - gptSlot1 = mockGpt.makeSlot({code: '/19968336/header-bid-tag1'}); + gptSlot1 = mockGpt.makeSlot({ code: '/19968336/header-bid-tag1' }); gptSlotRenderEnded1 = { eventName: 'slotRenderEnded', params: { @@ -1658,6 +1800,7 @@ describe('rubicon analytics adapter', function () { lineItemId: 6666, adSlot: '/19968336/header-bid-tag1' }; + expectedMessage.trigger = 'gam'; expect(message).to.deep.equal(expectedMessage); }); @@ -1902,6 +2045,7 @@ describe('rubicon analytics adapter', function () { let timedOutBid = message.auctions[0].adUnits[0].bids[0]; expect(timedOutBid.status).to.equal('error'); expect(timedOutBid.error.code).to.equal('timeout-error'); + expect(timedOutBid.error.description).to.equal('prebid.js timeout'); expect(timedOutBid).to.not.have.property('bidResponse'); }); @@ -1982,7 +2126,7 @@ describe('rubicon analytics adapter', function () { // Now add the bidResponse hook which hooks on the currenct conversion function onto the bid response let innerBid; - addBidResponseHook(function(adCodeId, bid) { + addBidResponseHook(function (adCodeId, bid) { innerBid = bid; }, 'elementId', bidCopy); @@ -1995,9 +2139,11 @@ describe('rubicon analytics adapter', function () { describe('config with integration type', () => { it('should use the integration type provided in the config instead of the default', () => { - config.setConfig({rubicon: { - int_type: 'testType' - }}) + config.setConfig({ + rubicon: { + int_type: 'testType' + } + }) rubiconAnalyticsAdapter.enableAnalytics({ options: { @@ -2019,11 +2165,13 @@ describe('rubicon analytics adapter', function () { describe('wrapper details passed in', () => { it('should correctly pass in the wrapper details if provided', () => { - config.setConfig({rubicon: { - wrapperName: '1001_wrapperName_exp.4', - wrapperFamily: '1001_wrapperName', - rule_name: 'na-mobile' - }}); + config.setConfig({ + rubicon: { + wrapperName: '1001_wrapperName_exp.4', + wrapperFamily: '1001_wrapperName', + rule_name: 'na-mobile' + } + }); rubiconAnalyticsAdapter.enableAnalytics({ options: { diff --git a/test/spec/modules/rubiconAnalyticsSchema.json b/test/spec/modules/rubiconAnalyticsSchema.json index 13d39e46cd0..39a33867edd 100644 --- a/test/spec/modules/rubiconAnalyticsSchema.json +++ b/test/spec/modules/rubiconAnalyticsSchema.json @@ -4,7 +4,6 @@ "description": "A batched data object describing the lifecycle of an auction or multiple auction across a single page view.", "type": "object", "required": [ - "eventTimeMillis", "integration", "version" ], @@ -21,10 +20,6 @@ } ], "properties": { - "eventTimeMillis": { - "type": "integer", - "description": "Unix timestamp of time of creation for this batched event in milliseconds." - }, "integration": { "type": "string", "description": "Integration type that generated this event.", @@ -268,7 +263,9 @@ }, "isSlotEmpty": { "type": "boolean", - "enum": [true] + "enum": [ + true + ] } } }, @@ -374,6 +371,7 @@ }, "error": { "type": "object", + "additionalProperties": false, "required": [ "code" ], @@ -453,4 +451,4 @@ } } } -} +} \ No newline at end of file diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 5669e03759a..1220ca345f0 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -229,7 +229,6 @@ describe('the rubicon adapter', function () { bid.userId = { lipb: {lipbid: '0000-1111-2222-3333', segments: ['segA', 'segB']}, idl_env: '1111-2222-3333-4444', - sharedid: {id: '1111', third: '2222'}, tdid: '3000', pubcid: '4000', pubProvidedId: [{ @@ -511,6 +510,38 @@ describe('the rubicon adapter', function () { expect(data['p_pos']).to.equal(undefined); }); + it('should not send p_pos to AE if not mediaTypes.banner.pos is invalid', function () { + var bidRequest = utils.deepClone(bidderRequest); + bidRequest.bids[0].mediaTypes = { + banner: { + pos: 5 + } + }; + delete bidRequest.bids[0].params.position; + + let [request] = spec.buildRequests(bidRequest.bids, bidRequest); + let data = parseQuery(request.data); + + expect(data['site_id']).to.equal('70608'); + expect(data['p_pos']).to.equal(undefined); + }); + + it('should send p_pos to AE if mediaTypes.banner.pos is valid', function () { + var bidRequest = utils.deepClone(bidderRequest); + bidRequest.bids[0].mediaTypes = { + banner: { + pos: 1 + } + }; + delete bidRequest.bids[0].params.position; + + let [request] = spec.buildRequests(bidRequest.bids, bidRequest); + let data = parseQuery(request.data); + + expect(data['site_id']).to.equal('70608'); + expect(data['p_pos']).to.equal('atf'); + }); + it('should not send p_pos to AE if not params.position is invalid', function () { var badposRequest = utils.deepClone(bidderRequest); badposRequest.bids[0].params.position = 'bad'; @@ -853,20 +884,33 @@ describe('the rubicon adapter', function () { 'ext': { 'segtax': 1 }, 'segment': [ { 'id': '987' } + ] + }, { + 'name': 'www.dataprovider1.com', + 'ext': { 'segtax': 2 }, + 'segment': [ + { 'id': '432' } + ] + }, { + 'name': 'www.dataprovider1.com', + 'ext': { 'segtax': 5 }, + 'segment': [ + { 'id': '55' } ] }, { 'name': 'www.dataprovider1.com', - 'ext': { 'segtax': 2 }, + 'ext': { 'segtax': 6 }, 'segment': [ - { 'id': '432' } + { 'id': '66' } ] - }] + } + ] } }; const user = { data: [{ 'name': 'www.dataprovider1.com', - 'ext': { 'segtax': 3 }, + 'ext': { 'segtax': 4 }, 'segment': [ { 'id': '687' }, { 'id': '123' } @@ -901,7 +945,7 @@ describe('the rubicon adapter', function () { 'tg_v.gender': 'M', 'tg_v.age': '40', 'tg_v.iab': '687,123', - 'tg_i.iab': '987,432', + 'tg_i.iab': '987,432,55,66', 'tg_v.yob': '1984', 'tg_i.rating': '4-star,5-star', 'tg_i.page': 'home', @@ -1226,23 +1270,6 @@ describe('the rubicon adapter', function () { }); }); - describe('SharedID support', function () { - it('should send sharedid when userIdAsEids contains sharedId', function () { - const clonedBid = utils.deepClone(bidderRequest.bids[0]); - clonedBid.userId = { - sharedid: { - id: '1111', - third: '2222' - } - }; - clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); - let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); - - expect(data['eid_sharedid.org']).to.equal('1111^1^2222'); - }); - }); - describe('pubProvidedId support', function () { it('should send pubProvidedId when userIdAsEids contains pubProvidedId ids', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); @@ -1312,10 +1339,7 @@ describe('the rubicon adapter', function () { config.setConfig({user: {id: '123'}}); const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { - sharedid: { - id: '1111', - third: '2222' - } + pubcid: '1111' }; let [request] = spec.buildRequests([clonedBid], bidderRequest); let data = parseQuery(request.data); @@ -1384,7 +1408,7 @@ describe('the rubicon adapter', function () { expect(data['tg_i.pbadslot']).to.equal('abc'); }); - it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string, but all leading slash characters should be removed', function () { + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { bidderRequest.bids[0].ortb2Imp = { ext: { data: { @@ -1398,7 +1422,45 @@ describe('the rubicon adapter', function () { expect(data).to.be.an('Object'); expect(data).to.have.property('tg_i.pbadslot'); - expect(data['tg_i.pbadslot']).to.equal('a/b/c'); + expect(data['tg_i.pbadslot']).to.equal('/a/b/c'); + }); + + it('should send gpid as p_gpid if valid', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + gpid: '/1233/sports&div1' + } + } + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('p_gpid'); + expect(data['p_gpid']).to.equal('/1233/sports&div1'); + }); + + it('should send gpid and pbadslot since it is prefered over dfp code', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + gpid: '/1233/sports&div1', + data: { + pbadslot: 'pb_slot', + adserver: { + adslot: '/1234/sports', + name: 'gam' + } + } + } + } + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data['p_gpid']).to.equal('/1233/sports&div1'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(data['tg_i.pbadslot']).to.equal('pb_slot'); }); }); @@ -1446,12 +1508,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string', function () { + it('should send NOT \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string but not gam', function () { bidderRequest.bids[0].ortb2Imp = { ext: { data: { adserver: { - adslot: 'abc' + adslot: '/a/b/c', + name: 'not gam' } } } @@ -1461,16 +1524,16 @@ describe('the rubicon adapter', function () { const data = parseQuery(request.data); expect(data).to.be.an('Object'); - expect(data).to.have.property('tg_i.dfp_ad_unit_code'); - expect(data['tg_i.dfp_ad_unit_code']).to.equal('abc'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string, but all leading slash characters should be removed', function () { + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string and name is gam', function () { bidderRequest.bids[0].ortb2Imp = { ext: { data: { adserver: { - adslot: 'a/b/c' + name: 'gam', + adslot: '/a/b/c' } } } @@ -1481,7 +1544,7 @@ describe('the rubicon adapter', function () { expect(data).to.be.an('Object'); expect(data).to.have.property('tg_i.dfp_ad_unit_code'); - expect(data['tg_i.dfp_ad_unit_code']).to.equal('a/b/c'); + expect(data['tg_i.dfp_ad_unit_code']).to.equal('/a/b/c'); }); }); }); @@ -1520,6 +1583,8 @@ describe('the rubicon adapter', function () { expect(imp.ext.rubicon.video.skip).to.equal(1); expect(imp.ext.rubicon.video.skipafter).to.equal(15); expect(imp.ext.prebid.auctiontimestamp).to.equal(1472239426000); + // should contain version + expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: 'v$prebid.version$'}); expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); // EIDs should exist expect(post.user.ext).to.have.property('eids').that.is.an('array'); @@ -1535,29 +1600,24 @@ describe('the rubicon adapter', function () { expect(post.user.ext.eids[1].source).to.equal('liveramp.com'); expect(post.user.ext.eids[1].uids[0].id).to.equal('1111-2222-3333-4444'); expect(post.user.ext.eids[1].uids[0].atype).to.equal(3); - // SharedId should exist - expect(post.user.ext.eids[2].source).to.equal('sharedid.org'); - expect(post.user.ext.eids[2].uids[0].id).to.equal('1111'); - expect(post.user.ext.eids[2].uids[0].atype).to.equal(1); - expect(post.user.ext.eids[2].uids[0].ext.third).to.equal('2222'); // UnifiedId should exist - expect(post.user.ext.eids[3].source).to.equal('adserver.org'); - expect(post.user.ext.eids[3].uids[0].atype).to.equal(1); - expect(post.user.ext.eids[3].uids[0].id).to.equal('3000'); + expect(post.user.ext.eids[2].source).to.equal('adserver.org'); + expect(post.user.ext.eids[2].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[2].uids[0].id).to.equal('3000'); // PubCommonId should exist - expect(post.user.ext.eids[4].source).to.equal('pubcid.org'); - expect(post.user.ext.eids[4].uids[0].atype).to.equal(1); - expect(post.user.ext.eids[4].uids[0].id).to.equal('4000'); + expect(post.user.ext.eids[3].source).to.equal('pubcid.org'); + expect(post.user.ext.eids[3].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[3].uids[0].id).to.equal('4000'); // example should exist - expect(post.user.ext.eids[5].source).to.equal('example.com'); - expect(post.user.ext.eids[5].uids[0].id).to.equal('333333'); + expect(post.user.ext.eids[4].source).to.equal('example.com'); + expect(post.user.ext.eids[4].uids[0].id).to.equal('333333'); // id-partner.com - expect(post.user.ext.eids[6].source).to.equal('id-partner.com'); - expect(post.user.ext.eids[6].uids[0].id).to.equal('4444444'); + expect(post.user.ext.eids[5].source).to.equal('id-partner.com'); + expect(post.user.ext.eids[5].uids[0].id).to.equal('4444444'); // CriteoId should exist - expect(post.user.ext.eids[7].source).to.equal('criteo.com'); - expect(post.user.ext.eids[7].uids[0].id).to.equal('1111'); - expect(post.user.ext.eids[7].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[6].source).to.equal('criteo.com'); + expect(post.user.ext.eids[6].uids[0].id).to.equal('1111'); + expect(post.user.ext.eids[6].uids[0].atype).to.equal(1); expect(post.regs.ext.gdpr).to.equal(1); expect(post.regs.ext.us_privacy).to.equal('1NYN'); @@ -3202,14 +3262,14 @@ describe('the rubicon adapter', function () { label: undefined, placement: { align: 'left', - attachTo: '#outstream_video1_placement', + attachTo: adUnit, position: 'append', }, vastUrl: 'https://test.com/vast.xml', width: 640 }); // cleanup - adUnit.remove(); + adUnit.parentNode.removeChild(adUnit); }); }); @@ -3322,6 +3382,23 @@ describe('the rubicon adapter', function () { type: 'iframe', url: `${emilyUrl}?gdpr_consent=foo&us_privacy=1NYN` }); }); + + it('should pass gdprApplies', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { + gdprApplies: true + }, '1NYN')).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gdpr=1&us_privacy=1NYN` + }); + }); + + it('should pass all correctly', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { + gdprApplies: true, + consentString: 'foo' + }, '1NYN')).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` + }); + }); }); describe('get price granularity', function () { diff --git a/test/spec/modules/saambaaBidAdapter_spec.js b/test/spec/modules/saambaaBidAdapter_spec.js deleted file mode 100755 index 80a85ae895d..00000000000 --- a/test/spec/modules/saambaaBidAdapter_spec.js +++ /dev/null @@ -1,139 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/saambaaBidAdapter.js'; -import { BANNER, VIDEO } from 'src/mediaTypes.js'; - -describe('saambaaBidAdapter', function () { - let bidRequests; - let bidRequestsVid; - - beforeEach(function () { - bidRequests = [{'bidder': 'saambaa', 'params': {'pubid': '121ab139faf7ac67428a23f1d0a9a71b', 'floor': 0.5, 'placement': 1234, size: '320x250'}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'f72931e6-2b0e-4e37-a2bc-1ea912141f81', 'sizes': [[300, 250]], 'bidId': '2aa73f571eaf29', 'bidderRequestId': '1bac84515a7af3', 'auctionId': '5dbc60fa-1aa1-41ce-9092-e6bbd4d478f7', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; - - bidRequestsVid = [{'bidder': 'saambaa', 'params': {'pubid': '121ab139faf7ac67428a23f1d0a9a71b', 'floor': 1.0, 'placement': 1234, size: '320x480', 'video': {'id': 123, 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream'}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; - }); - - describe('spec.isBidRequestValid', function () { - it('should return true when the required params are passed for banner', function () { - const bidRequest = bidRequests[0]; - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); - }); - - it('should return true when the required params are passed for video', function () { - const bidRequests = bidRequestsVid[0]; - expect(spec.isBidRequestValid(bidRequests)).to.equal(true); - }); - - it('should return false when no pub id params are passed', function () { - const bidRequest = bidRequests[0]; - bidRequest.params.pubid = ''; - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); - - it('should return false when no placement params are passed', function () { - const bidRequest = bidRequests[0]; - bidRequest.params.placement = ''; - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); - - it('should return false when a bid request is not passed', function () { - expect(spec.isBidRequestValid()).to.equal(false); - expect(spec.isBidRequestValid({})).to.equal(false); - }); - }); - - describe('spec.buildRequests', function () { - it('should create a POST request for each bid', function () { - const bidRequest = bidRequests[0]; - const requests = spec.buildRequests([ bidRequest ]); - expect(requests[0].method).to.equal('POST'); - }); - - it('should create a POST request for each bid in video request', function () { - const bidRequest = bidRequestsVid[0]; - const requests = spec.buildRequests([ bidRequest ]); - expect(requests[0].method).to.equal('POST'); - }); - - it('should have domain in request', function () { - const bidRequest = bidRequests[0]; - const requests = spec.buildRequests([ bidRequest ]); - expect(requests[0].data.site.domain).length !== 0; - }); - }); - - describe('spec.interpretResponse', function () { - describe('for banner bids', function () { - it('should return no bids if the response is not valid', function () { - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { banner: {} }; - const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); - - if (typeof bidResponse !== 'undefined') { - expect(bidResponse.length).to.equal(0); - } else { - expect(true).to.equal(true); - } - }); - - it('should return no bids if the response is empty', function () { - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { banner: {} }; - const bidResponse = spec.interpretResponse({ body: [] }, { bidRequest }); - if (typeof bidResponse !== 'undefined') { - expect(bidResponse.length).to.equal(0); - } else { expect(true).to.equal(true); } - }); - - it('should return valid video bid responses', function () { - let _mediaTypes = VIDEO; - const saambaabidreqVid = {'bidRequest': {'mediaTypes': {'video': {'w': 320, 'h': 480}}}}; - const serverResponseVid = {'cur': 'USD', 'id': '25c6ab92aa0e81', 'seatbid': [{'seat': '3', 'bid': [{'crid': '1855', 'h': 480, 'protocol': 2, 'nurl': 'http://nep.advangelists.com/xp/evt?pp=1MO1wiaMhhq7wLRzZZwwwPkJxxKpYEnM5k5MH4qSGm1HR8rp3Nl7vDocvzZzSAvE4pnREL9mQ1kf5PDjk6E8em6DOk7vVrYUH1TYQyqCucd58PFpJNN7h30RXKHHFg3XaLuQ3PKfMuI1qZATBJ6WHcu875y0hqRdiewn0J4JsCYF53M27uwmcV0HnQxARQZZ72mPqrW95U6wgkZljziwKrICM3aBV07TU6YK5R5AyzJRuD6mtrQ2xtHlQ3jXVYKE5bvWFiUQd90t0jOGhPtYBNoOfP7uQ4ZZj4pyucxbr96orHe9PSOn9UpCSWArdx7s8lOfDpwOvbMuyGxynbStDWm38sDgd4bMHnIt762m5VMDNJfiUyX0vWzp05OsufJDVEaWhAM62i40lQZo7mWP4ipoOWLkmlaAzFIMsTcNaHAHiKKqGEOZLkCEhFNM0SLcvgN2HFRULOOIZvusq7TydOKxuXgCS91dLUDxDDDFUK83BFKlMkTxnCzkLbIR1bd9GKcr1TRryOrulyvRWAKAIhEsUzsc5QWFUhmI2dZ1eqnBQJ0c89TaPcnoaP2WipF68UgyiOstf2CBy0M34858tC5PmuQwQYwXscg6zyqDwR0i9MzGH4FkTyU5yeOlPcsA0ht6UcoCdFpHpumDrLUwAaxwGk1Nj8S6YlYYT5wNuTifDGbg22QKXzZBkUARiyVvgPn9nRtXnrd7WmiMYq596rya9RQj7LC0auQW8bHVQLEe49shsZDnAwZTWr4QuYKqgRGZcXteG7RVJe0ryBZezOq11ha9C0Lv0siNVBahOXE35Wzoq4c4BDaGpqvhaKN7pjeWLGlQR04ufWekwxiMWAvjmfgAfexBJ7HfbYNZpq__', 'adid': '61_1855', 'adomain': ['chevrolet.com'], 'price': 2, 'w': 320, 'iurl': 'https://daf37cpxaja7f.cloudfront.net/c61/creative_url_14922301369663_1.png', 'cat': ['IAB2'], 'id': '7f570b40-aca1-4806-8ea8-818ea679c82b_0', 'attr': [], 'impid': '0', 'cid': '61'}]}], 'bidid': '7f570b40-aca1-4806-8ea8-818ea679c82b'} - const bidResponseVid = spec.interpretResponse({ body: serverResponseVid }, saambaabidreqVid); - delete bidResponseVid['vastUrl']; - delete bidResponseVid['ad']; - expect(bidResponseVid).to.deep.equal({ - requestId: bidRequestsVid[0].bidId, - bidderCode: 'saambaa', - creativeId: serverResponseVid.seatbid[0].bid[0].crid, - cpm: serverResponseVid.seatbid[0].bid[0].price, - width: serverResponseVid.seatbid[0].bid[0].w, - height: serverResponseVid.seatbid[0].bid[0].h, - mediaType: 'video', - meta: { advertiserDomains: serverResponseVid.seatbid[0].bid[0].adomain }, - currency: 'USD', - netRevenue: true, - ttl: 60 - }); - }); - - it('should return valid banner bid responses', function () { - const saambaabidreq = {bids: {}}; - bidRequests.forEach(bid => { - let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); - saambaabidreq.bids[bid.bidId] = {mediaTypes: _mediaTypes, - w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], - h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] - - }; - }); - const serverResponse = {'id': '2aa73f571eaf29', 'seatbid': [{'bid': [{'id': '2c5e8a1a84522d', 'impid': '2c5e8a1a84522d', 'price': 0.81, 'adid': 'abcde-12345', 'nurl': '', 'adm': '
', 'adomain': ['advertiserdomain.com'], 'iurl': '', 'cid': 'campaign1', 'crid': 'abcde-12345', 'w': 300, 'h': 250}], 'seat': '19513bcfca8006'}], 'bidid': '19513bcfca8006', 'cur': 'USD', 'w': 300, 'h': 250}; - - const bidResponse = spec.interpretResponse({ body: serverResponse }, saambaabidreq); - expect(bidResponse).to.deep.equal({ - requestId: bidRequests[0].bidId, - ad: serverResponse.seatbid[0].bid[0].adm, - bidderCode: 'saambaa', - creativeId: serverResponse.seatbid[0].bid[0].crid, - cpm: serverResponse.seatbid[0].bid[0].price, - width: serverResponse.seatbid[0].bid[0].w, - height: serverResponse.seatbid[0].bid[0].h, - mediaType: 'banner', - meta: { advertiserDomains: serverResponse.seatbid[0].bid[0].adomain }, - currency: 'USD', - netRevenue: true, - ttl: 60 - }); - }); - }); - }); -}); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index c82c8300d2e..159645ff6d6 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -16,7 +16,8 @@ function getSlotConfigs(mediaTypes, params) { bidder: 'seedtag', mediaTypes: mediaTypes, src: 'client', - transactionId: 'd704d006-0d6e-4a09-ad6c-179e7e758096' + transactionId: 'd704d006-0d6e-4a09-ad6c-179e7e758096', + adUnitCode: 'adunit-code' } } @@ -55,7 +56,7 @@ describe('Seedtag Adapter', function() { }) }) describe('when video slot has all mandatory params', function() { - it('should return true, when video mediatype object are correct.', function() { + it('should return true, when video context is instream', function () { const slotConfig = getSlotConfigs( { video: { @@ -72,6 +73,24 @@ describe('Seedtag Adapter', function() { const isBidRequestValid = spec.isBidRequestValid(slotConfig) expect(isBidRequestValid).to.equal(true) }) + + it('should return true, when video context is outstream', function () { + const slotConfig = getSlotConfigs( + { + video: { + context: 'outstream', + playerSize: [[600, 200]] + } + }, + { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, + placement: 'video' + } + ) + const isBidRequestValid = spec.isBidRequestValid(slotConfig) + expect(isBidRequestValid).to.equal(true) + }) }) }) describe('returns false', function() { @@ -137,7 +156,7 @@ describe('Seedtag Adapter', function() { ) expect(isBidRequestValid).to.equal(false) }) - it('is not instream ', function() { + it('is outstream ', function () { const isBidRequestValid = spec.isBidRequestValid( createVideoSlotConfig({ video: { @@ -146,7 +165,7 @@ describe('Seedtag Adapter', function() { } }) ) - expect(isBidRequestValid).to.equal(false) + expect(isBidRequestValid).to.equal(true) }) describe('order does not matter', function() { it('when video is not the first slot', function() { @@ -201,6 +220,7 @@ describe('Seedtag Adapter', function() { expect(data.publisherToken).to.equal('0000-0000-01') expect(typeof data.version).to.equal('string') expect(['fixed', 'mobile', 'unknown'].indexOf(data.connectionType)).to.be.above(-1) + expect(data.bidRequests[0].adUnitCode).to.equal('adunit-code') }) describe('adPosition param', function() { @@ -254,6 +274,9 @@ describe('Seedtag Adapter', function() { expect(data.ga).to.equal(true) expect(data.cd).to.equal('consentString') }) + it('should expose gvlid', function() { + expect(spec.gvlid).to.equal(157) + }) }) }) @@ -273,6 +296,7 @@ describe('Seedtag Adapter', function() { expect(bannerBid.sizes[0][1]).to.equal(250) expect(bannerBid.sizes[1][0]).to.equal(300) expect(bannerBid.sizes[1][1]).to.equal(600) + expect(bannerBid.requestCount).to.equal(1) }) it('should request an InStream Video', function() { const videoBid = bidRequests[1] @@ -289,6 +313,7 @@ describe('Seedtag Adapter', function() { expect(videoBid.sizes[0][1]).to.equal(250) expect(videoBid.sizes[1][0]).to.equal(300) expect(videoBid.sizes[1][1]).to.equal(600) + expect(videoBid.requestCount).to.equal(1) }) }) }) @@ -326,7 +351,8 @@ describe('Seedtag Adapter', function() { height: 90, mediaType: 'display', ttl: 360, - nurl: 'testurl.com/nurl' + nurl: 'testurl.com/nurl', + adomain: ['advertiserdomain.com'] } ], cookieSync: { url: '' } @@ -342,6 +368,7 @@ describe('Seedtag Adapter', function() { expect(bids[0].netRevenue).to.equal(true) expect(bids[0].ad).to.equal('content') expect(bids[0].nurl).to.equal('testurl.com/nurl') + expect(bids[0].meta.advertiserDomains).to.deep.equal(['advertiserdomain.com']) }) }) describe('the bid is a video', function() { @@ -374,6 +401,7 @@ describe('Seedtag Adapter', function() { expect(bids[0].currency).to.equal('USD') expect(bids[0].netRevenue).to.equal(true) expect(bids[0].vastXml).to.equal('content') + expect(bids[0].meta.advertiserDomains).to.deep.equal([]) }) }) }) diff --git a/test/spec/modules/segmentoBidAdapter_spec.js b/test/spec/modules/segmentoBidAdapter_spec.js deleted file mode 100644 index 17ad424f73f..00000000000 --- a/test/spec/modules/segmentoBidAdapter_spec.js +++ /dev/null @@ -1,187 +0,0 @@ -import { expect } from 'chai'; -import { spec } from '../../../modules/segmentoBidAdapter.js'; - -const BIDDER_CODE = 'segmento'; -const URL = 'https://prebid-bidder.rutarget.ru/bid'; -const SYNC_IFRAME_URL = 'https://tag.rutarget.ru/tag?event=otherPage&check=true&response=syncframe&synconly=true'; -const SYNC_IMAGE_URL = 'https://tag.rutarget.ru/tag?event=otherPage&check=true&synconly=true'; -const RUB = 'RUB'; -const TIME_TO_LIVE = 0; - -describe('SegmentoAdapter', function () { - describe('isBidRequestValid', function () { - const bid = { - bidder: BIDDER_CODE, - bidId: '51ef8751f9aead', - params: { - placementId: 34 - }, - adUnitCode: 'div-gpt-ad-1460505748561-0', - transactionId: 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - sizes: [[320, 50], [300, 250], [300, 600]], - bidderRequestId: '418b37f85e772c', - auctionId: '18fd8b8b0bd757' - }; - - it('should return true if placementId is a number', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false if placementId is not a number', function () { - bid.params.placementId = 'placementId'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false if no placementId param', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - const bids = [{ - bidder: 'segmento', - bidId: '51ef8751f9aead', - params: { - placementId: 34 - }, - adUnitCode: 'div-gpt-ad-1460505748561-0', - transactionId: 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - sizes: [[320, 50], [300, 250], [300, 600]], - bidderRequestId: '418b37f85e772c', - auctionId: '18fd8b8b0bd757' - }]; - - const bidderRequest = { - refererInfo: { - referer: 'https://comepage.com' - } - }; - - const request = spec.buildRequests(bids, bidderRequest); - it('should return POST method', function () { - expect(request.method).to.equal('POST'); - }); - - it('should return valid url', function () { - expect(request.url).to.equal(URL); - }); - - it('should return valid data', function () { - const data = request.data; - expect(data).to.have.all.keys('settings', 'places'); - expect(data.settings.currency).to.be.equal(RUB); - expect(data.settings.referrer).to.be.a('string'); - expect(data.settings.referrer).to.be.equal(bidderRequest.refererInfo.referer); - const places = data.places; - for (let i = 0; i < places.length; i++) { - const place = places[i]; - const bid = bids[i]; - expect(place).to.have.all.keys('id', 'placementId', 'sizes'); - expect(place.id).to.be.a('string'); - expect(place.id).to.be.equal(bid.bidId); - expect(place.placementId).to.be.a('number'); - expect(place.placementId).to.be.equal(bid.params.placementId); - expect(place.sizes).to.be.an('array'); - expect(place.sizes).to.deep.equal(bid.sizes); - } - }); - - it('should return empty places if no valid bids are passed', function () { - const request = spec.buildRequests([], {}); - expect(request.data.places).to.be.an('array').to.deep.equal([]); - }); - }); - - describe('interpretResponse', function() { - const serverResponse = { - body: { - bids: [{ - id: '51ef8751f9aead', - cpm: 0.23, - currency: RUB, - creativeId: 123, - displayUrl: 'displayUrl?t=123&p=456', - size: { - width: 300, - height: 250 - } - }] - } - }; - - const emptyServerResponse = { - body: { - bids: [] - } - }; - - it('should return valid data', function () { - const response = spec.interpretResponse(serverResponse); - expect(response).to.be.an('array'); - for (let i = 0; i < response.length; i++) { - const item = response[i]; - const bid = serverResponse.body.bids[i]; - expect(item).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'creativeId', - 'currency', 'netRevenue', 'ttl', 'adUrl'); - expect(item.requestId).to.be.a('string'); - expect(item.requestId).to.be.equal(bid.id); - expect(item.cpm).to.be.a('number'); - expect(item.cpm).to.be.equal(bid.cpm); - expect(item.width).to.be.a('number'); - expect(item.width).to.be.equal(bid.size.width); - expect(item.height).to.be.a('number'); - expect(item.height).to.be.equal(bid.size.height); - expect(item.creativeId).to.be.a('number'); - expect(item.creativeId).to.be.equal(bid.creativeId); - expect(item.currency).to.be.a('string'); - expect(item.currency).to.be.equal(bid.currency); - expect(item.netRevenue).to.be.a('boolean'); - expect(item.netRevenue).to.equal(true); - expect(item.ttl).to.be.a('number'); - expect(item.ttl).to.be.equal(TIME_TO_LIVE); - expect(item.adUrl).to.be.a('string'); - expect(item.adUrl).to.be.equal(bid.displayUrl); - } - }); - - it('should return empty array if no bids', function () { - const response = spec.interpretResponse(emptyServerResponse); - expect(response).to.be.an('array').to.deep.equal([]); - }); - - it('should return empty array if server response is invalid', function () { - const response = spec.interpretResponse({}); - expect(response).to.be.an('array').to.deep.equal([]); - }); - }); - - describe('getUserSyncs', function() { - it('should return iframe type if iframe enabled', function () { - const syncs = spec.getUserSyncs({ iframeEnabled: true }); - const sync = syncs[0]; - expect(syncs).to.be.an('array').with.lengthOf(1); - expect(sync).to.have.all.keys('type', 'url'); - expect(sync.type).to.be.a('string'); - expect(sync.type).to.be.equal('iframe'); - expect(sync.url).to.be.a('string'); - expect(sync.url).to.be.equal(SYNC_IFRAME_URL); - }); - - it('should return iframe type if iframe disabled, but image enable', function () { - const syncs = spec.getUserSyncs({ pixelEnabled: true }); - const sync = syncs[0]; - expect(syncs).to.be.an('array').with.lengthOf(1); - expect(sync).to.have.all.keys('type', 'url'); - expect(sync.type).to.be.a('string'); - expect(sync.type).to.be.equal('image'); - expect(sync.url).to.be.a('string'); - expect(sync.url).to.be.equal(SYNC_IMAGE_URL); - }); - - it('should return empty array if iframe and pixels disabled', function () { - const syncs = spec.getUserSyncs({}); - expect(syncs).to.be.an('array').to.deep.equal([]); - }); - }); -}); diff --git a/test/spec/modules/sekindoUMBidAdapter_spec.js b/test/spec/modules/sekindoUMBidAdapter_spec.js deleted file mode 100644 index 2c361c21303..00000000000 --- a/test/spec/modules/sekindoUMBidAdapter_spec.js +++ /dev/null @@ -1,168 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/sekindoUMBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -describe('sekindoUMAdapter', function () { - const adapter = newBidder(spec); - - const bannerParams = { - 'spaceId': '14071' - }; - - const videoParams = { - 'spaceId': '14071', - 'video': { - playerWidth: 300, - playerHeight: 250, - vid_vastType: 2 // optional - } - }; - - var bidRequests = { - 'bidder': 'sekindoUM', - 'params': bannerParams, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'mediaType': 'banner' - }; - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { - bidRequests.mediaType = 'banner'; - bidRequests.params = bannerParams; - expect(spec.isBidRequestValid(bidRequests)).to.equal(true); - }); - - it('should return false when required video params are missing', function () { - bidRequests.mediaType = 'video'; - bidRequests.params = bannerParams; - expect(spec.isBidRequestValid(bidRequests)).to.equal(false); - }); - - it('should return true when required Video params found', function () { - bidRequests.mediaType = 'video'; - bidRequests.params = videoParams; - expect(spec.isBidRequestValid(bidRequests)).to.equal(true); - }); - }); - - describe('buildRequests', function () { - it('banner data should be a query string and method = GET', function () { - bidRequests.mediaType = 'banner'; - bidRequests.params = bannerParams; - const request = spec.buildRequests([bidRequests]); - expect(request[0].data).to.be.a('string'); - expect(request[0].method).to.equal('GET'); - }); - - it('with gdprConsent, banner data should be a query string and method = GET', function () { - bidRequests.mediaType = 'banner'; - bidRequests.params = bannerParams; - const request = spec.buildRequests([bidRequests], {'gdprConsent': {'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', 'vendorData': {}, 'gdprApplies': true}}); - expect(request[0].data).to.be.a('string'); - expect(request[0].method).to.equal('GET'); - }); - - it('video data should be a query string and method = GET', function () { - bidRequests.mediaType = 'video'; - bidRequests.params = videoParams; - const request = spec.buildRequests([bidRequests]); - expect(request[0].data).to.be.a('string'); - expect(request[0].method).to.equal('GET'); - }); - }); - - describe('interpretResponse', function () { - it('banner should get correct bid response', function () { - let response = { - 'headers': function(header) { - return 'dummy header'; - }, - 'body': {'id': '30b31c1838de1e', 'bidderCode': 'sekindoUM', 'cpm': 2.1951, 'width': 300, 'height': 250, 'ad': '

sekindo creative<\/h1>', 'ttl': 36000, 'creativeId': '323774', 'netRevenue': true, 'currency': 'USD'} - }; - - bidRequests.mediaType = 'banner'; - bidRequests.params = bannerParams; - let expectedResponse = [ - { - 'requestId': '30b31c1838de1e', - 'bidderCode': 'sekindoUM', - 'cpm': 2.1951, - 'width': 300, - 'height': 250, - 'creativeId': '323774', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 36000, - 'ad': '

sekindo creative

' - } - ]; - let result = spec.interpretResponse(response, bidRequests); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); - }); - - it('vastXml video should get correct bid response', function () { - let response = { - 'headers': function(header) { - return 'dummy header'; - }, - 'body': {'id': '30b31c1838de1e', 'bidderCode': 'sekindoUM', 'cpm': 2.1951, 'width': 300, 'height': 250, 'vastXml': '', 'ttl': 36000, 'creativeId': '323774', 'netRevenue': true, 'currency': 'USD'} - }; - - bidRequests.mediaType = 'video'; - bidRequests.params = videoParams; - let expectedResponse = [ - { - 'requestId': '30b31c1838de1e', - 'bidderCode': 'sekindoUM', - 'cpm': 2.1951, - 'width': 300, - 'height': 250, - 'creativeId': '323774', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 36000, - 'vastXml': '' - } - ]; - let result = spec.interpretResponse(response, bidRequests); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); - }); - - it('vastUrl video should get correct bid response', function () { - let response = { - 'headers': function(header) { - return 'dummy header'; - }, - 'body': {'id': '30b31c1838de1e', 'bidderCode': 'sekindoUM', 'cpm': 2.1951, 'width': 300, 'height': 250, 'vastUrl': 'https://vastUrl', 'ttl': 36000, 'creativeId': '323774', 'netRevenue': true, 'currency': 'USD'} - }; - bidRequests.mediaType = 'video'; - bidRequests.params = videoParams; - let expectedResponse = [ - { - 'requestId': '30b31c1838de1e', - 'bidderCode': 'sekindoUM', - 'cpm': 2.1951, - 'width': 300, - 'height': 250, - 'creativeId': '323774', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 36000, - 'vastUrl': 'https://vastUrl' - } - ]; - let result = spec.interpretResponse(response, bidRequests); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); - }); - }); -}); diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index ad51fe81cde..8ef34a1599e 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -1,77 +1,140 @@ -import { - sharedIdSubmodule, -} from 'modules/sharedIdSystem.js'; -import { server } from 'test/mocks/xhr.js'; -import {uspDataHandler} from 'src/adapterManager'; +import {sharedIdSystemSubmodule, storage} from 'modules/sharedIdSystem.js'; +import {coppaDataHandler} from 'src/adapterManager'; + +import sinon from 'sinon'; +import * as utils from 'src/utils.js'; let expect = require('chai').expect; -describe('SharedId System', function() { - const SHAREDID_RESPONSE = {sharedId: 'testsharedid'}; - let uspConsentDataStub; - describe('Xhr Requests from getId()', function() { - let callbackSpy = sinon.spy(); +describe('SharedId System', function () { + const UUID = '15fde1dc-1861-4894-afdf-b757272f3568'; - beforeEach(function() { - callbackSpy.resetHistory(); - uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); - }); - - afterEach(function () { - uspConsentDataStub.restore(); - }); + before(function () { + sinon.stub(utils, 'generateUUID').returns(UUID); + sinon.stub(utils, 'logInfo'); + }); - it('should call shared id endpoint without consent data and handle a valid response', function () { - let submoduleCallback = sharedIdSubmodule.getId(undefined, undefined).callback; - submoduleCallback(callbackSpy); + after(function () { + utils.generateUUID.restore(); + utils.logInfo.restore(); + }); + describe('SharedId System getId()', function () { + const callbackSpy = sinon.spy(); - let request = server.requests[0]; - expect(request.url).to.equal('https://id.sharedid.org/id'); - expect(request.withCredentials).to.be.true; + let coppaDataHandlerDataStub + let sandbox; - request.respond(200, {}, JSON.stringify(SHAREDID_RESPONSE)); + beforeEach(function () { + sandbox = sinon.sandbox.create(); + coppaDataHandlerDataStub = sandbox.stub(coppaDataHandler, 'getCoppa'); + sandbox.stub(utils, 'hasDeviceAccess').returns(true); + coppaDataHandlerDataStub.returns(''); + callbackSpy.resetHistory(); + }); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg.id).to.equal(SHAREDID_RESPONSE.sharedId); + afterEach(function () { + sandbox.restore(); }); - it('should call shared id endpoint with consent data and handle a valid response', function () { - let consentData = { - gdprApplies: true, - consentString: 'abc12345234', + it('should call UUID', function () { + let config = { + storage: { + type: 'cookie', + name: '_pubcid', + expires: 10 + } }; - let submoduleCallback = sharedIdSubmodule.getId(undefined, consentData).callback; + let submoduleCallback = sharedIdSystemSubmodule.getId(config, undefined).callback; submoduleCallback(callbackSpy); - - let request = server.requests[0]; - expect(request.url).to.equal('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234'); - expect(request.withCredentials).to.be.true; - - request.respond(200, {}, JSON.stringify(SHAREDID_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg.id).to.equal(SHAREDID_RESPONSE.sharedId); + expect(callbackSpy.lastCall.lastArg).to.equal(UUID); }); - - it('should call shared id endpoint with usp consent data and handle a valid response', function () { - uspConsentDataStub.returns('1YYY'); - let consentData = { - gdprApplies: true, - consentString: 'abc12345234', + it('should abort if coppa is set', function () { + coppaDataHandlerDataStub.returns('true'); + const result = sharedIdSystemSubmodule.getId({}); + expect(result).to.be.undefined; + }); + }); + describe('SharedId System extendId()', function () { + const callbackSpy = sinon.spy(); + let coppaDataHandlerDataStub; + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + coppaDataHandlerDataStub = sandbox.stub(coppaDataHandler, 'getCoppa'); + sandbox.stub(utils, 'hasDeviceAccess').returns(true); + callbackSpy.resetHistory(); + coppaDataHandlerDataStub.returns(''); + }); + afterEach(function () { + sandbox.restore(); + }); + it('should call UUID', function () { + let config = { + params: { + extend: true + }, + storage: { + type: 'cookie', + name: '_pubcid', + expires: 10 + } }; + let pubcommId = sharedIdSystemSubmodule.extendId(config, undefined, 'TestId').id; + expect(pubcommId).to.equal('TestId'); + }); + it('should abort if coppa is set', function () { + coppaDataHandlerDataStub.returns('true'); + const result = sharedIdSystemSubmodule.extendId({params: {extend: true}}, undefined, 'TestId'); + expect(result).to.be.undefined; + }); + }); - let submoduleCallback = sharedIdSubmodule.getId(undefined, consentData).callback; - submoduleCallback(callbackSpy); + describe('SharedID System domainOverride', () => { + let sandbox, domain, cookies, rejectCookiesFor; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(document, 'domain').get(() => domain); + cookies = {}; + sandbox.stub(storage, 'getCookie').callsFake((key) => cookies[key]); + rejectCookiesFor = null; + sandbox.stub(storage, 'setCookie').callsFake((key, value, expires, sameSite, domain) => { + if (domain !== rejectCookiesFor) { + if (expires != null) { + expires = new Date(expires); + } + if (expires == null || expires > Date.now()) { + cookies[key] = value; + } else { + delete cookies[key]; + } + } + }); + }); + + afterEach(() => { + sandbox.restore(); + }); - let request = server.requests[0]; - expect(request.url).to.equal('https://id.sharedid.org/id?us_privacy=1YYY&gdpr=1&gdpr_consent=abc12345234'); - expect(request.withCredentials).to.be.true; + it('should return TLD if cookies can be set there', () => { + domain = 'sub.domain.com'; + rejectCookiesFor = 'com'; + expect(sharedIdSystemSubmodule.domainOverride()).to.equal('domain.com'); + }); - request.respond(200, {}, JSON.stringify(SHAREDID_RESPONSE)); + it('should return undefined when cookies cannot be set', () => { + domain = 'sub.domain.com'; + rejectCookiesFor = 'sub.domain.com'; + expect(sharedIdSystemSubmodule.domainOverride()).to.be.undefined; + }); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg.id).to.equal(SHAREDID_RESPONSE.sharedId); + it('should return half-way domain if parent domain rejects cookies', () => { + domain = 'inner.outer.domain.com'; + rejectCookiesFor = 'domain.com'; + expect(sharedIdSystemSubmodule.domainOverride()).to.equal('outer.domain.com'); }); }); }); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index f741f985f9f..39238cc877f 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -1,622 +1,649 @@ import { expect } from 'chai'; +import * as sinon from 'sinon'; import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import * as utils from '../../../src/utils.js'; import { config } from 'src/config'; +import * as utils from 'src/utils'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); -const bidRequests = [ - { - bidder: 'sharethrough', - bidId: 'bidId1', - sizes: [[600, 300]], - placementCode: 'foo', - params: { - pkey: 'aaaa1111' - }, - userId: { - tdid: 'fake-tdid', - pubcid: 'fake-pubcid', - idl_env: 'fake-identity-link', - id5id: { - uid: 'fake-id5id', - ext: { - linkType: 2 - } - }, - sharedid: { - id: 'fake-sharedid', - third: 'fake-sharedthird' - }, - lipb: { - lipbid: 'fake-lipbid' - } - }, - crumbs: { - pubcid: 'fake-pubcid-in-crumbs-obj' - } - }, - { - bidder: 'sharethrough', - bidId: 'bidId2', - sizes: [[700, 400]], - placementCode: 'bar', - params: { - pkey: 'bbbb2222', - iframe: true - } - }, - { - bidder: 'sharethrough', - bidId: 'bidId3', - sizes: [[700, 400]], - placementCode: 'coconut', - params: { - pkey: 'cccc3333', - iframe: true, - iframeSize: [500, 500] - } - }, - { - bidder: 'sharethrough', - bidId: 'bidId4', - sizes: [[700, 400]], - placementCode: 'bar', - params: { - pkey: 'dddd4444', - badv: ['domain1.com', 'domain2.com'] - } - }, - { - bidder: 'sharethrough', - bidId: 'bidId5', - sizes: [[700, 400]], - placementCode: 'bar', - params: { - pkey: 'eeee5555', - bcat: ['IAB1-1', 'IAB1-2'] - } - }, -]; - -const prebidRequests = [ - { - method: 'POST', - url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', - data: { - bidId: 'bidId', - placement_key: 'pKey' - }, - strData: { - skipIframeBusting: false, - sizes: [] - } - }, - { - method: 'POST', - url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', - data: { - bidId: 'bidId', - placement_key: 'pKey' - }, - strData: { - skipIframeBusting: true, - sizes: [[300, 250], [300, 300], [250, 250], [600, 50]] - } - }, - { - method: 'POST', - url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', - data: { - bidId: 'bidId', - placement_key: 'pKey' - }, - strData: { - skipIframeBusting: true, - iframeSize: [500, 500], - sizes: [[300, 250], [300, 300], [250, 250], [600, 50]] - } - }, - { - method: 'POST', - url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', - data: { - bidId: 'bidId', - placement_key: 'pKey' - }, - strData: { - skipIframeBusting: false, - sizes: [[0, 0]] - } - }, - { - method: 'POST', - url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', - data: { - bidId: 'bidId', - placement_key: 'pKey' - }, - strData: { - skipIframeBusting: false, - sizes: [[300, 250], [300, 300], [250, 250], [600, 50]] - } - } -]; - -const bidderResponse = { - body: { - 'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994', - 'bidId': 'bidId1', - 'version': 1, - 'creatives': [{ - 'auctionWinId': 'b2882d5e-bf8b-44da-a91c-0c11287b8051', - 'cpm': 12.34, - 'creative': { - 'deal_id': 'aDealId', - 'creative_key': 'aCreativeId', - 'title': '✓ à la mode' - } - }], - 'stxUserId': '' - }, - header: { get: (header) => header } -}; - -const setUserAgent = (uaString) => { - window.navigator['__defineGetter__']('userAgent', function() { - return uaString; - }); -}; - -describe('sharethrough internal spec', function() { - let windowStub, windowTopStub; - let stubbedReturn = [{ - appendChild: () => undefined - }] - beforeEach(function() { - windowStub = sinon.stub(window.document, 'getElementsByTagName'); - windowTopStub = sinon.stub(window.top.document, 'getElementsByTagName'); - windowStub.withArgs('body').returns(stubbedReturn); - windowTopStub.withArgs('body').returns(stubbedReturn); - }); - - afterEach(function() { - windowStub.restore(); - windowTopStub.restore(); - window.STR = undefined; - window.top.STR = undefined; - }); - - describe('we cannot access top level document', function() { - beforeEach(function() { - window.lockedInFrame = true; - }); - - afterEach(function() { - window.lockedInFrame = false; - }); - it('appends sfp.js to the safeframe', function() { - sharethroughInternal.handleIframe(); - expect(windowStub.calledOnce).to.be.true; - }); +describe('sharethrough adapter spec', function () { + let protocolStub; + let inIframeStub; - it('does not append anything if sfp.js is already loaded in the safeframe', function() { - window.STR = { Tag: true }; - sharethroughInternal.handleIframe(); - expect(windowStub.notCalled).to.be.true; - expect(windowTopStub.notCalled).to.be.true; - }); + beforeEach(() => { + protocolStub = sinon.stub(sharethroughInternal, 'getProtocol').returns('https'); + inIframeStub = sinon.stub(utils, 'inIframe').returns(false); }); - describe('we are able to bust out of the iframe', function() { - it('appends sfp.js to window.top', function() { - sharethroughInternal.handleIframe(); - expect(windowStub.calledOnce).to.be.true; - expect(windowTopStub.calledOnce).to.be.true; - }); - - it('only appends sfp-set-targeting.js if sfp.js is already loaded on the page', function() { - window.top.STR = { Tag: true }; - sharethroughInternal.handleIframe(); - expect(windowStub.calledOnce).to.be.true; - expect(windowTopStub.notCalled).to.be.true; - }); + afterEach(() => { + protocolStub.restore(); + inIframeStub.restore(); }); -}); -describe('sharethrough adapter spec', function() { - describe('.code', function() { - it('should return a bidder code of sharethrough', function() { + describe('code', function () { + it('should return a bidder code of sharethrough', function () { expect(spec.code).to.eql('sharethrough'); }); }); - describe('.isBidRequestValid', function() { - it('should return false if req has no pkey', function() { + describe('isBidRequestValid', function () { + it('should return false if req has no pkey', function () { const invalidBidRequest = { bidder: 'sharethrough', params: { - notPKey: 'abc123' - } + notPKey: 'abc123', + }, }; expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); - it('should return false if req has wrong bidder code', function() { + it('should return false if req has wrong bidder code', function () { const invalidBidRequest = { bidder: 'notSharethrough', params: { - notPKey: 'abc123' - } + pkey: 'abc123', + }, }; expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); - it('should return true if req is correct', function() { - expect(spec.isBidRequestValid(bidRequests[0])).to.eq(true); - expect(spec.isBidRequestValid(bidRequests[1])).to.eq(true); + it('should return true if req is correct', function () { + const validBidRequest = { + bidder: 'sharethrough', + params: { + pkey: 'abc123', + }, + }; + expect(spec.isBidRequestValid(validBidRequest)).to.eq(true); }); }); - describe('.buildRequests', function() { - it('should return an array of requests', function() { - const builtBidRequests = spec.buildRequests(bidRequests); + describe('open rtb', () => { + let bidRequests, bidderRequest; - expect(builtBidRequests[0].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); - expect(builtBidRequests[1].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); - expect(builtBidRequests[0].method).to.eq('POST'); + beforeEach(() => { + config.setConfig({ + bidderTimeout: 242, + coppa: true, + }); + + bidRequests = [ + { + bidder: 'sharethrough', + bidId: 'bidId1', + sizes: [[300, 250], [300, 600]], + params: { + pkey: 'aaaa1111', + bcat: ['cat1', 'cat2'], + badv: ['adv1', 'adv2'], + }, + mediaTypes: { + banner: { + pos: 1, + }, + }, + ortb2Imp: { + ext: { + data: { + pbadslot: 'universal-id', + }, + }, + }, + userId: { + tdid: 'fake-tdid', + pubcid: 'fake-pubcid', + idl_env: 'fake-identity-link', + id5id: { + uid: 'fake-id5id', + ext: { + linkType: 2, + }, + }, + lipb: { + lipbid: 'fake-lipbid', + }, + criteoId: 'fake-criteo', + britepoolid: 'fake-britepool', + intentIqId: 'fake-intentiq', + lotamePanoramaId: 'fake-lotame', + parrableId: { + eid: 'fake-parrable', + }, + netId: 'fake-netid', + sharedid: { + id: 'fake-sharedid', + }, + flocId: { + id: 'fake-flocid', + version: '42', + }, + }, + crumbs: { + pubcid: 'fake-pubcid-in-crumbs-obj', + }, + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1, + }, + ], + }, + getFloor: () => ({ currency: 'USD', floor: 42 }), + }, + { + bidder: 'sharethrough', + bidId: 'bidId2', + sizes: [[600, 300]], + params: { + pkey: 'bbbb2222', + }, + mediaTypes: { + video: { + pos: 3, + skip: 1, + linearity: 0, + minduration: 10, + maxduration: 30, + playbackmethod: [1], + api: [3], + mimes: ['video/3gpp'], + protocols: [2, 3], + playerSize: [640, 480], + startdelay: 42, + skipmin: 10, + skipafter: 20, + delivery: 1, + companiontype: 'companion type', + companionad: 'companion ad', + context: 'instream', + }, + }, + getFloor: () => ({ currency: 'USD', floor: 42 }), + }, + ]; + + bidderRequest = { + refererInfo: { + referer: 'https://referer.com', + }, + }; }); - it('should set the instant_play_capable parameter correctly based on browser userAgent string', function() { - setUserAgent('Android Chrome/60'); - let builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].data.instant_play_capable).to.be.true; + describe('buildRequests', function () { + describe('top level object', () => { + it('should build openRTB request', () => { + const builtRequests = spec.buildRequests(bidRequests, bidderRequest); + + const expectedImpValues = [ + { + id: 'bidId1', + tagid: 'aaaa1111', + secure: 1, + bidfloor: 42, + }, + { + id: 'bidId2', + tagid: 'bbbb2222', + secure: 1, + bidfloor: 42, + }, + ]; + + builtRequests.map((builtRequest, rIndex) => { + expect(builtRequest.method).to.equal('POST'); + expect(builtRequest.url).not.to.be.undefined; + expect(builtRequest.options).to.be.undefined; + + const openRtbReq = builtRequest.data; + expect(openRtbReq.id).not.to.be.undefined; + expect(openRtbReq.cur).to.deep.equal(['USD']); + expect(openRtbReq.tmax).to.equal(242); + + expect(Object.keys(openRtbReq.site)).to.have.length(3); + expect(openRtbReq.site.domain).not.to.be.undefined; + expect(openRtbReq.site.page).not.to.be.undefined; + expect(openRtbReq.site.ref).to.equal('https://referer.com'); + + const expectedEids = { + 'liveramp.com': { id: 'fake-identity-link' }, + 'id5-sync.com': { id: 'fake-id5id' }, + 'pubcid.org': { id: 'fake-pubcid' }, + 'adserver.org': { id: 'fake-tdid' }, + 'criteo.com': { id: 'fake-criteo' }, + 'britepool.com': { id: 'fake-britepool' }, + 'liveintent.com': { id: 'fake-lipbid' }, + 'intentiq.com': { id: 'fake-intentiq' }, + 'crwdcntrl.net': { id: 'fake-lotame' }, + 'parrable.com': { id: 'fake-parrable' }, + 'netid.de': { id: 'fake-netid' }, + 'chrome.com': { id: 'fake-flocid' }, + }; + expect(openRtbReq.user.ext.eids).to.be.an('array').that.have.length(Object.keys(expectedEids).length); + for (const eid of openRtbReq.user.ext.eids) { + expect(Object.keys(expectedEids)).to.include(eid.source); + expect(eid.uids[0].id).to.equal(expectedEids[eid.source].id); + expect(eid.uids[0].atype).to.be.ok; + } + + expect(openRtbReq.device.ua).to.equal(navigator.userAgent); + expect(openRtbReq.regs.coppa).to.equal(1); + + expect(openRtbReq.source.ext.version).not.to.be.undefined; + expect(openRtbReq.source.ext.str).not.to.be.undefined; + expect(openRtbReq.source.ext.schain).to.deep.equal(bidRequests[0].schain); + + expect(openRtbReq.bcat).to.deep.equal(bidRequests[0].params.bcat); + expect(openRtbReq.badv).to.deep.equal(bidRequests[0].params.badv); + + expect(openRtbReq.imp).to.have.length(1); + + expect(openRtbReq.imp[0].id).to.equal(expectedImpValues[rIndex].id); + expect(openRtbReq.imp[0].tagid).to.equal(expectedImpValues[rIndex].tagid); + expect(openRtbReq.imp[0].secure).to.equal(expectedImpValues[rIndex].secure); + expect(openRtbReq.imp[0].bidfloor).to.equal(expectedImpValues[rIndex].bidfloor); + }); + }); - setUserAgent('iPhone Version/11'); - builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].data.instant_play_capable).to.be.true; + it('should have empty eid array if no id is provided', () => { + const openRtbReq = spec.buildRequests([bidRequests[1]], bidderRequest)[0].data; - setUserAgent('iPhone CriOS/60'); - builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].data.instant_play_capable).to.be.true; + expect(openRtbReq.user.ext.eids).to.deep.equal([]); + }); + }); - setUserAgent('Android Chrome/50'); - builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].data.instant_play_capable).to.be.false; + describe('no referer provided', () => { + beforeEach(() => { + bidderRequest = {}; + }); - setUserAgent('Android Chrome'); - builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].data.instant_play_capable).to.be.false; + it('should set referer to undefined', () => { + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; + expect(openRtbReq.site.ref).to.be.undefined; + }); + }); - setUserAgent(undefined); - builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].data.instant_play_capable).to.be.false; - }); + describe('regulation', () => { + describe('gdpr', () => { + it('should populate request accordingly when gdpr applies', () => { + bidderRequest.gdprConsent = { + gdprApplies: true, + consentString: 'consent', + }; - it('should set the secure parameter to false when the protocol is http', function() { - const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('http:'); - const bidRequest = spec.buildRequests(bidRequests, null)[0]; - expect(bidRequest.data.secure).to.be.false; - stub.restore(); - }); + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; - it('should set the secure parameter to true when the protocol is https', function() { - const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('https:'); - const bidRequest = spec.buildRequests(bidRequests, null)[0]; - expect(bidRequest.data.secure).to.be.true; - stub.restore(); - }); + expect(openRtbReq.regs.ext.gdpr).to.equal(1); + expect(openRtbReq.user.ext.consent).to.equal('consent'); + }); - it('should set the secure parameter to true when the protocol is neither http or https', function() { - const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('about:'); - const bidRequest = spec.buildRequests(bidRequests, null)[0]; - expect(bidRequest.data.secure).to.be.true; - stub.restore(); - }); + it('should populate request accordingly when gdpr explicitly does not apply', () => { + bidderRequest.gdprConsent = { + gdprApplies: false, + }; - it('should add ccpa parameter if uspConsent is present', function() { - const uspConsent = '1YNN'; - const bidderRequest = { uspConsent: uspConsent }; - const bidRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(bidRequest.data.us_privacy).to.eq(uspConsent); - }); + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; - it('should add consent parameters if gdprConsent is present', function() { - const gdprConsent = { consentString: 'consent_string123', gdprApplies: true }; - const bidderRequest = { gdprConsent: gdprConsent }; - const bidRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(bidRequest.data.consent_required).to.eq(true); - expect(bidRequest.data.consent_string).to.eq('consent_string123'); - }); + expect(openRtbReq.regs.ext.gdpr).to.equal(0); + expect(openRtbReq.user.ext.consent).to.be.undefined; + }); + }); - it('should handle gdprConsent is present but values are undefined case', function() { - const gdprConsent = { consent_string: undefined, gdprApplies: undefined }; - const bidderRequest = { gdprConsent: gdprConsent }; - const bidRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(bidRequest.data).to.not.include.any.keys('consent_string'); - }); + describe('US privacy', () => { + it('should populate request accordingly when us privacy applies', () => { + bidderRequest.uspConsent = 'consent'; - it('should add the ttduid parameter if a bid request contains a value for Unified ID from The Trade Desk', function() { - const bidRequest = spec.buildRequests(bidRequests)[0]; - expect(bidRequest.data.ttduid).to.eq('fake-tdid'); - }); + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; - it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + - ' userId object of the bidrequest', function() { - const bidRequest = spec.buildRequests(bidRequests)[0]; - expect(bidRequest.data.pubcid).to.eq('fake-pubcid'); - }); + expect(openRtbReq.regs.ext.us_privacy).to.equal('consent'); + }); + }); - it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + - ' crumbs object of the bidrequest', function() { - const bidData = utils.deepClone(bidRequests); - delete bidData[0].userId.pubcid; + describe('coppa', () => { + it('should populate request accordingly when coppa does not apply', () => { + config.setConfig({ coppa: false }); - const bidRequest = spec.buildRequests(bidData)[0]; - expect(bidRequest.data.pubcid).to.eq('fake-pubcid-in-crumbs-obj'); - }); + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; - it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + - ' crumbs object of the bidrequest', function() { - const bidRequest = spec.buildRequests(bidRequests)[0]; - delete bidRequest.userId; - expect(bidRequest.data.pubcid).to.eq('fake-pubcid'); - }); + expect(openRtbReq.regs.coppa).to.equal(0); + }); + }); + }); - it('should add the idluid parameter if a bid request contains a value for Identity Link from Live Ramp', function() { - const bidRequest = spec.buildRequests(bidRequests)[0]; - expect(bidRequest.data.idluid).to.eq('fake-identity-link'); - }); + describe('universal id', () => { + it('should include gpid when universal id is provided', () => { + const requests = spec.buildRequests(bidRequests, bidderRequest); - it('should add the id5uid parameter if a bid request contains a value for ID5', function() { - const bidRequest = spec.buildRequests(bidRequests)[0]; - expect(bidRequest.data.id5uid.id).to.eq('fake-id5id'); - expect(bidRequest.data.id5uid.linkType).to.eq(2); - }); + expect(requests[0].data.imp[0].ext.gpid).to.equal('universal-id'); + expect(requests[1].data.imp[0].ext).to.be.undefined; + }); + }); - it('should add the shduid parameter if a bid request contains a value for Shared ID', function() { - const bidRequest = spec.buildRequests(bidRequests)[0]; - expect(bidRequest.data.shduid.id).to.eq('fake-sharedid'); - expect(bidRequest.data.shduid.third).to.eq('fake-sharedthird'); - }); + describe('secure flag', () => { + it('should be positive when protocol is https', () => { + protocolStub.returns('https'); + const requests = spec.buildRequests(bidRequests, bidderRequest); - it('should add the liuid parameter if a bid request contains a value for LiveIntent ID', function() { - const bidRequest = spec.buildRequests(bidRequests)[0]; - expect(bidRequest.data.liuid).to.eq('fake-lipbid'); - }); + expect(requests[0].data.imp[0].secure).to.equal(1); + expect(requests[1].data.imp[0].secure).to.equal(1); + }); - it('should add Sharethrough specific parameters', function() { - const builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0]).to.deep.include({ - strData: { - skipIframeBusting: undefined, - iframeSize: undefined, - sizes: [[600, 300]] - } - }); - }); + it('should be negative when protocol is http', () => { + protocolStub.returns('http'); + const requests = spec.buildRequests(bidRequests, bidderRequest); - it('should add a supply chain parameter if schain is present', function() { - // shallow copy of the first bidRequest obj, so we don't mutate - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest['schain'] = { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'directseller.com', - sid: '00001', - rid: 'BidRequest1', - hp: 1 - } - ] - }; + expect(requests[0].data.imp[0].secure).to.equal(0); + expect(requests[1].data.imp[0].secure).to.equal(0); + }); - const builtBidRequest = spec.buildRequests([bidRequest])[0]; - expect(builtBidRequest.data.schain).to.eq(JSON.stringify(bidRequest.schain)); - }); + it('should be positive when protocol is neither http nor https', () => { + protocolStub.returns('about'); + const requests = spec.buildRequests(bidRequests, bidderRequest); - it('should add badv if provided', () => { - const builtBidRequest = spec.buildRequests([bidRequests[3]])[0]; + expect(requests[0].data.imp[0].secure).to.equal(1); + expect(requests[1].data.imp[0].secure).to.equal(1); + }); + }); - expect(builtBidRequest.data.badv).to.have.members(['domain1.com', 'domain2.com']) - }); + describe('banner imp', () => { + it('should generate open rtb banner imp', () => { + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; - it('should add bcat if provided', () => { - const builtBidRequest = spec.buildRequests([bidRequests[4]])[0]; + const bannerImp = builtRequest.data.imp[0].banner; + expect(bannerImp.pos).to.equal(1); + expect(bannerImp.topframe).to.equal(1); + expect(bannerImp.format).to.deep.equal([{ w: 300, h: 250 }, { w: 300, h: 600 }]); + }); - expect(builtBidRequest.data.bcat).to.have.members(['IAB1-1', 'IAB1-2']) - }); + it('should default to pos 0 if not provided', () => { + delete bidRequests[0].mediaTypes; + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; - it('should not add a supply chain parameter if schain is missing', function() { - const bidRequest = spec.buildRequests(bidRequests)[0]; - expect(bidRequest.data).to.not.include.any.keys('schain'); - }); + const bannerImp = builtRequest.data.imp[0].banner; + expect(bannerImp.pos).to.equal(0); + }); + }); - it('should include the bidfloor parameter if it is present in the bid request', function() { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest['bidfloor'] = 0.50; - const builtBidRequest = spec.buildRequests([bidRequest])[0]; - expect(builtBidRequest.data.bidfloor).to.eq(0.5); - }); + describe('video imp', () => { + it('should generate open rtb video imp', () => { + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + + const videoImp = builtRequest.data.imp[0].video; + expect(videoImp.pos).to.equal(3); + expect(videoImp.topframe).to.equal(1); + expect(videoImp.skip).to.equal(1); + expect(videoImp.linearity).to.equal(0); + expect(videoImp.minduration).to.equal(10); + expect(videoImp.maxduration).to.equal(30); + expect(videoImp.playbackmethod).to.deep.equal([1]); + expect(videoImp.api).to.deep.equal([3]); + expect(videoImp.mimes).to.deep.equal(['video/3gpp']); + expect(videoImp.protocols).to.deep.equal([2, 3]); + expect(videoImp.w).to.equal(640); + expect(videoImp.h).to.equal(480); + expect(videoImp.startdelay).to.equal(42); + expect(videoImp.skipmin).to.equal(10); + expect(videoImp.skipafter).to.equal(20); + expect(videoImp.placement).to.equal(1); + expect(videoImp.delivery).to.equal(1); + expect(videoImp.companiontype).to.equal('companion type'); + expect(videoImp.companionad).to.equal('companion ad'); + }); - it('should not include the bidfloor parameter if it is missing in the bid request', function() { - const bidRequest = Object.assign({}, bidRequests[0]); - const builtBidRequest = spec.buildRequests([bidRequest])[0]; - expect(builtBidRequest.data).to.not.include.any.keys('bidfloor'); - }); + it('should set defaults if no value provided', () => { + delete bidRequests[1].mediaTypes.video.pos; + delete bidRequests[1].mediaTypes.video.skip; + delete bidRequests[1].mediaTypes.video.linearity; + delete bidRequests[1].mediaTypes.video.minduration; + delete bidRequests[1].mediaTypes.video.maxduration; + delete bidRequests[1].mediaTypes.video.playbackmethod; + delete bidRequests[1].mediaTypes.video.api; + delete bidRequests[1].mediaTypes.video.mimes; + delete bidRequests[1].mediaTypes.video.protocols; + delete bidRequests[1].mediaTypes.video.playerSize; + delete bidRequests[1].mediaTypes.video.startdelay; + delete bidRequests[1].mediaTypes.video.skipmin; + delete bidRequests[1].mediaTypes.video.skipafter; + delete bidRequests[1].mediaTypes.video.placement; + delete bidRequests[1].mediaTypes.video.delivery; + delete bidRequests[1].mediaTypes.video.companiontype; + delete bidRequests[1].mediaTypes.video.companionad; + + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + + const videoImp = builtRequest.data.imp[0].video; + expect(videoImp.pos).to.equal(0); + expect(videoImp.skip).to.equal(0); + expect(videoImp.linearity).to.equal(1); + expect(videoImp.minduration).to.equal(5); + expect(videoImp.maxduration).to.equal(60); + expect(videoImp.playbackmethod).to.deep.equal([2]); + expect(videoImp.api).to.deep.equal([2]); + expect(videoImp.mimes).to.deep.equal(['video/mp4']); + expect(videoImp.protocols).to.deep.equal([2, 3, 5, 6, 7, 8]); + expect(videoImp.w).to.equal(640); + expect(videoImp.h).to.equal(360); + expect(videoImp.startdelay).to.equal(0); + expect(videoImp.skipmin).to.equal(0); + expect(videoImp.skipafter).to.equal(0); + expect(videoImp.placement).to.equal(1); + expect(videoImp.delivery).to.be.undefined; + expect(videoImp.companiontype).to.be.undefined; + expect(videoImp.companionad).to.be.undefined; + }); - describe('coppa', function() { - it('should add coppa to request if enabled', function() { - config.setConfig({coppa: true}); - const bidRequest = Object.assign({}, bidRequests[0]); - const builtBidRequest = spec.buildRequests([bidRequest])[0]; - expect(builtBidRequest.data.coppa).to.eq(true); - }); + describe('outstream', () => { + it('should use placement value if provided', () => { + bidRequests[1].mediaTypes.video.context = 'outstream'; + bidRequests[1].mediaTypes.video.placement = 3; - it('should not add coppa to request if disabled', function() { - config.setConfig({coppa: false}); - const bidRequest = Object.assign({}, bidRequests[0]); - const builtBidRequest = spec.buildRequests([bidRequest])[0]; - expect(builtBidRequest.data.coppa).to.be.undefined; - }); + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + const videoImp = builtRequest.data.imp[0].video; - it('should not add coppa to request if unknown value', function() { - config.setConfig({coppa: 'something'}); - const bidRequest = Object.assign({}, bidRequests[0]); - const builtBidRequest = spec.buildRequests([bidRequest])[0]; - expect(builtBidRequest.data.coppa).to.be.undefined; - }); - }); - }); + expect(videoImp.placement).to.equal(3); + }); - describe('.interpretResponse', function() { - it('returns a correctly parsed out response', function() { - expect(spec.interpretResponse(bidderResponse, prebidRequests[0])[0]).to.include( - { - width: 1, - height: 1, - cpm: 12.34, - creativeId: 'aCreativeId', - dealId: 'aDealId', - currency: 'USD', - netRevenue: true, - ttl: 360 - }); - }); + it('should default placement to 4 if not provided', () => { + bidRequests[1].mediaTypes.video.context = 'outstream'; - it('returns a correctly parsed out response with largest size when strData.skipIframeBusting is true', function() { - expect(spec.interpretResponse(bidderResponse, prebidRequests[1])[0]).to.include( - { - width: 300, - height: 300, - cpm: 12.34, - creativeId: 'aCreativeId', - dealId: 'aDealId', - currency: 'USD', - netRevenue: true, - ttl: 360 + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + const videoImp = builtRequest.data.imp[0].video; + + expect(videoImp.placement).to.equal(4); + }); }); - }); + }); - it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is true and strData.iframeSize is provided', function() { - expect(spec.interpretResponse(bidderResponse, prebidRequests[2])[0]).to.include( - { - width: 500, - height: 500, - cpm: 12.34, - creativeId: 'aCreativeId', - dealId: 'aDealId', - currency: 'USD', - netRevenue: true, - ttl: 360 + describe('first party data', () => { + const firstPartyData = { + site: { + name: 'example', + keywords: 'power tools, drills', + search: 'drill', + content: { + userrating: '4', + }, + ext: { + data: { + pageType: 'article', + category: 'repair', + }, + }, + }, + user: { + yob: 1985, + gender: 'm', + ext: { + data: { + registered: true, + interests: ['cars'], + }, + }, + }, + }; + + let configStub; + + beforeEach(() => { + configStub = sinon.stub(config, 'getConfig'); + configStub.withArgs('ortb2').returns(firstPartyData); }); - }); - it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is false and strData.sizes contains [0, 0] only', function() { - expect(spec.interpretResponse(bidderResponse, prebidRequests[3])[0]).to.include( - { - width: 0, - height: 0, - cpm: 12.34, - creativeId: 'aCreativeId', - dealId: 'aDealId', - currency: 'USD', - netRevenue: true, - ttl: 360 + afterEach(() => { + configStub.restore(); }); - }); - it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is false and strData.sizes contains multiple sizes', function() { - expect(spec.interpretResponse(bidderResponse, prebidRequests[4])[0]).to.include( - { - width: 300, - height: 300, - cpm: 12.34, - creativeId: 'aCreativeId', - dealId: 'aDealId', - currency: 'USD', - netRevenue: true, - ttl: 360 + it('should include first party data in open rtb request, site section', () => { + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; + + expect(openRtbReq.site.name).to.equal(firstPartyData.site.name); + expect(openRtbReq.site.keywords).to.equal(firstPartyData.site.keywords); + expect(openRtbReq.site.search).to.equal(firstPartyData.site.search); + expect(openRtbReq.site.content).to.deep.equal(firstPartyData.site.content); + expect(openRtbReq.site.ext).to.deep.equal(firstPartyData.site.ext); }); - }); - it('returns a blank array if there are no creatives', function() { - const bidResponse = { body: { creatives: [] } }; - expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty; - }); + it('should include first party data in open rtb request, user section', () => { + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; - it('returns a blank array if body object is empty', function() { - const bidResponse = { body: {} }; - expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty; + expect(openRtbReq.user.yob).to.equal(firstPartyData.user.yob); + expect(openRtbReq.user.gender).to.equal(firstPartyData.user.gender); + expect(openRtbReq.user.ext.data).to.deep.equal(firstPartyData.user.ext.data); + expect(openRtbReq.user.ext.eids).not.to.be.undefined; + }); + }); }); - it('returns a blank array if body is null', function() { - const bidResponse = { body: null }; - expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty; - }); + describe('interpretResponse', function () { + let request; + let response; + + describe('banner', () => { + beforeEach(() => { + request = spec.buildRequests(bidRequests, bidderRequest)[0]; + response = { + body: { + seatbid: [{ + bid: [{ + id: '123', + impid: 'bidId1', + w: 300, + h: 250, + price: 42, + crid: 'creative', + dealid: 'deal', + adomain: ['domain.com'], + adm: 'markup', + }, { + id: '456', + impid: 'bidId2', + w: 640, + h: 480, + price: 42, + adm: 'vastTag', + }], + }], + }, + }; + }); - it('correctly generates ad markup when skipIframeBusting is false', function() { - const adMarkup = spec.interpretResponse(bidderResponse, prebidRequests[0])[0].ad; - let resp = null; + it('should return a banner bid', () => { + const resp = spec.interpretResponse(response, request); + + const bannerBid = resp[0]; + expect(bannerBid.requestId).to.equal('bidId1'); + expect(bannerBid.width).to.equal(300); + expect(bannerBid.height).to.equal(250); + expect(bannerBid.cpm).to.equal(42); + expect(bannerBid.creativeId).to.equal('creative'); + expect(bannerBid.dealId).to.equal('deal'); + expect(bannerBid.mediaType).to.equal('banner'); + expect(bannerBid.currency).to.equal('USD'); + expect(bannerBid.netRevenue).to.equal(true); + expect(bannerBid.ttl).to.equal(360); + expect(bannerBid.ad).to.equal('markup'); + expect(bannerBid.meta.advertiserDomains).to.deep.equal(['domain.com']); + expect(bannerBid.vastXml).to.be.undefined; + }); + }); - expect(() => btoa(JSON.stringify(bidderResponse))).to.throw(); - expect(() => resp = sharethroughInternal.b64EncodeUnicode(JSON.stringify(bidderResponse))).not.to.throw(); - expect(adMarkup).to.match( - /data-str-native-key="pKey" data-stx-response-name="str_response_bidId"/); - expect(!!adMarkup.indexOf(resp)).to.eql(true); + describe('video', () => { + beforeEach(() => { + request = spec.buildRequests(bidRequests, bidderRequest)[1]; + response = { + body: { + seatbid: [{ + bid: [{ + id: '456', + impid: 'bidId2', + w: 640, + h: 480, + price: 42, + adm: 'vastTag', + }], + }], + }, + }; + }); - // insert functionality to autodetect whether or not in safeframe, and handle JS insertion - expect(adMarkup).to.match(/isLockedInFrame/); - expect(adMarkup).to.match(/handleIframe/); + it('should return a video bid', () => { + const resp = spec.interpretResponse(response, request); + + const bannerBid = resp[0]; + expect(bannerBid.requestId).to.equal('bidId2'); + expect(bannerBid.width).to.equal(640); + expect(bannerBid.height).to.equal(480); + expect(bannerBid.cpm).to.equal(42); + expect(bannerBid.creativeId).to.be.undefined; + expect(bannerBid.dealId).to.be.null; + expect(bannerBid.mediaType).to.equal('video'); + expect(bannerBid.currency).to.equal('USD'); + expect(bannerBid.netRevenue).to.equal(true); + expect(bannerBid.ttl).to.equal(3600); + expect(bannerBid.ad).to.equal('vastTag'); + expect(bannerBid.meta.advertiserDomains).to.deep.equal([]); + expect(bannerBid.vastXml).to.equal('vastTag'); + }); + }); }); - it('correctly generates ad markup when skipIframeBusting is true', function() { - const adMarkup = spec.interpretResponse(bidderResponse, prebidRequests[1])[0].ad; - let resp = null; - - expect(() => btoa(JSON.stringify(bidderResponse))).to.throw(); - expect(() => resp = sharethroughInternal.b64EncodeUnicode(JSON.stringify(bidderResponse))).not.to.throw(); - expect(adMarkup).to.match( - /data-str-native-key="pKey" data-stx-response-name="str_response_bidId"/); - expect(!!adMarkup.indexOf(resp)).to.eql(true); - expect(adMarkup).to.match( - /"'; - describe('slimcutBidAdapter', function() { const adapter = newBidder(spec); - describe('inherited functions', function() { it('exists and is a function', function() { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); - describe('isBidRequestValid', function() { let bid = { 'bidder': 'slimcut', @@ -21,75 +23,66 @@ describe('slimcutBidAdapter', function() { 'placementId': 83 }, 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], + 'sizes': [ + [300, 250], + [300, 600] + ], 'bidId': '3c871ffa8ef14c', 'bidderRequestId': 'b41642f1aee381', 'auctionId': '4e156668c977d7' }; - it('should return true when required params found', function() { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return false when placementId is not valid (letters)', function() { let bid = Object.assign({}, bid); delete bid.params; bid.params = { 'placementId': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when placementId < 0', function() { let bid = Object.assign({}, bid); delete bid.params; bid.params = { 'placementId': -1 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when required params are not passed', function() { let bid = Object.assign({}, bid); delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); - describe('buildRequests', function() { - let bidRequests = [ - { - 'bidder': 'teads', - 'params': { - 'placementId': 10433394 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '3c871ffa8ef14c', - 'bidderRequestId': 'b41642f1aee381', - 'auctionId': '4e156668c977d7', - 'deviceWidth': 1680 - } - ]; - + let bidRequests = [{ + 'bidder': 'teads', + 'params': { + 'placementId': 10433394 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '3c871ffa8ef14c', + 'bidderRequestId': 'b41642f1aee381', + 'auctionId': '4e156668c977d7', + 'deviceWidth': 1680 + }]; let bidderResquestDefault = { 'auctionId': '4e156668c977d7', 'bidderRequestId': 'b41642f1aee381', 'timeout': 3000 }; - it('sends bid request to ENDPOINT via POST', function() { const request = spec.buildRequests(bidRequests, bidderResquestDefault); - expect(request.url).to.equal(ENDPOINT); expect(request.method).to.equal('POST'); }); - it('should send GDPR to endpoint', function() { let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; let bidderRequest = { @@ -104,15 +97,12 @@ describe('slimcutBidAdapter', function() { } } }; - const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); - expect(payload.gdpr_iab).to.exist; expect(payload.gdpr_iab.consent).to.equal(consentString); }); - - it('should add referer info to payload', function () { + it('should add referer info to payload', function() { const bidRequest = Object.assign({}, bidRequests[0]) const bidderRequest = { refererInfo: { @@ -123,12 +113,10 @@ describe('slimcutBidAdapter', function() { } const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); - expect(payload.referrer).to.exist; expect(payload.referrer).to.deep.equal('https://example.com/page.html') }); }); - describe('getUserSyncs', () => { let bids = { 'body': { @@ -147,19 +135,20 @@ describe('slimcutBidAdapter', function() { }] } }; - it('should get the correct number of sync urls', () => { - let urls = spec.getUserSyncs({iframeEnabled: true}, bids); + let urls = spec.getUserSyncs({ + iframeEnabled: true + }, bids); expect(urls.length).to.equal(1); expect(urls[0].url).to.equal('https://sb.freeskreen.com/async_usersync.html'); }); - it('should return no url if not iframe enabled', () => { - let urls = spec.getUserSyncs({iframeEnabled: false}, bids); + let urls = spec.getUserSyncs({ + iframeEnabled: false + }, bids); expect(urls.length).to.equal(0); }); }); - describe('interpretResponse', function() { let bids = { 'body': { @@ -178,7 +167,6 @@ describe('slimcutBidAdapter', function() { }] } }; - it('should get correct bid response', function() { let expectedResponse = [{ 'cpm': 0.5, @@ -191,20 +179,20 @@ describe('slimcutBidAdapter', function() { 'requestId': '3ede2a3fa0db94', 'creativeId': 'er2ee', 'transactionId': 'deadb33f', - 'winUrl': 'https://sb.freeskreen.com/win' + 'winUrl': 'https://sb.freeskreen.com/win', + 'meta': { + 'advertiserDomains': [] + } }]; - let result = spec.interpretResponse(bids); expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); }); - it('handles nobid responses', function() { let bids = { 'body': { 'responses': [] } }; - let result = spec.interpretResponse(bids); expect(result.length).to.equal(0); }); diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 1bc77fc9572..9803d1df103 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -1,202 +1,32 @@ -import { spec } from 'modules/smaatoBidAdapter.js'; +import {spec} from 'modules/smaatoBidAdapter.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; import {createEidsArray} from 'modules/userId/eids.js'; -const imageAd = { - image: { - img: { - url: 'https://prebid/static/ad.jpg', - w: 320, - h: 50, - ctaurl: 'https://prebid/track/ctaurl' - }, - impressiontrackers: [ - 'https://prebid/track/imp/1', - 'https://prebid/track/imp/2' - ], - clicktrackers: [ - 'https://prebid/track/click/1' - ] - } -}; - -const richmediaAd = { - richmedia: { - mediadata: { - content: '

RICHMEDIA CONTENT

', - w: 800, - h: 600 - }, - impressiontrackers: [ - 'https://prebid/track/imp/1', - 'https://prebid/track/imp/2' - ], - clicktrackers: [ - 'https://prebid/track/click/1' - ] - } -}; - const ADTYPE_IMG = 'Img'; const ADTYPE_RICHMEDIA = 'Richmedia'; const ADTYPE_VIDEO = 'Video'; -const site = { - keywords: 'power tools,drills' -}; - -const user = { - keywords: 'a,b', - gender: 'M', - yob: 1984 -}; - -const openRtbBidResponse = (adType) => { - let adm = ''; - - switch (adType) { - case ADTYPE_IMG: - adm = JSON.stringify(imageAd); - break; - case ADTYPE_RICHMEDIA: - adm = JSON.stringify(richmediaAd); - break; - case ADTYPE_VIDEO: - adm = ''; - break; - default: - throw Error('Invalid AdType'); - } - - let resp = { - body: { - bidid: '04db8629-179d-4bcd-acce-e54722969006', - cur: 'USD', - ext: {}, - id: '5ebea288-f13a-4754-be6d-4ade66c68877', - seatbid: [ - { - bid: [ - { - 'adm': adm, - 'adomain': [ - 'smaato.com' - ], - 'bidderName': 'smaato', - 'cid': 'CM6523', - 'crid': 'CR69381', - 'dealid': '12345', - 'id': '6906aae8-7f74-4edd-9a4f-f49379a3cadd', - 'impid': '226416e6e6bf41', - 'iurl': 'https://prebid/iurl', - 'nurl': 'https://prebid/nurl', - 'price': 0.01, - 'w': 350, - 'h': 50 - } - ], - seat: 'CM6523' - } - ], - }, - headers: { - get: function (header) { - if (header === 'X-SMT-ADTYPE') { - return adType; - } - } - } - }; - return resp; -}; - -const request = { - method: 'POST', - url: 'https://prebid.ad.smaato.net/oapi/prebid', - data: '' -}; - -const interpretedBidsImg = [ - { - requestId: '226416e6e6bf41', - cpm: 0.01, - width: 350, - height: 50, - ad: '
\"\"\"\"
', - ttl: 300, - creativeId: 'CR69381', - dealId: '12345', - netRevenue: true, - currency: 'USD', - meta: { - advertiserDomains: ['smaato.com'], - agencyId: 'CM6523', - networkName: 'smaato', - mediaType: 'banner' - } - } -]; - -const interpretedBidsRichmedia = [ - { - requestId: '226416e6e6bf41', - cpm: 0.01, - width: 350, - height: 50, - ad: '

RICHMEDIA CONTENT

\"\"\"\"
', - ttl: 300, - creativeId: 'CR69381', - dealId: '12345', - netRevenue: true, - currency: 'USD', - meta: { - advertiserDomains: ['smaato.com'], - agencyId: 'CM6523', - networkName: 'smaato', - mediaType: 'banner' - } - } -]; - -const interpretedBidsVideo = [ - { - requestId: '226416e6e6bf41', - cpm: 0.01, - width: 350, - height: 50, - vastXml: '', - ttl: 300, - creativeId: 'CR69381', - dealId: '12345', - netRevenue: true, - currency: 'USD', - meta: { - advertiserDomains: ['smaato.com'], - agencyId: 'CM6523', - networkName: 'smaato', - mediaType: 'video' - } - } -]; +const REFERRER = 'http://example.com/page.html' +const CONSENT_STRING = 'HFIDUYFIUYIUYWIPOI87392DSU' +const AUCTION_ID = '6653'; const defaultBidderRequest = { gdprConsent: { - consentString: 'HFIDUYFIUYIUYWIPOI87392DSU', + consentString: CONSENT_STRING, gdprApplies: true }, uspConsent: 'uspConsentString', refererInfo: { - referer: 'http://example.com/page.html', + referer: REFERRER, }, - timeout: 1200 + timeout: 1200, + auctionId: AUCTION_ID }; -const minimalBidderRequest = { - refererInfo: { - referer: 'http://example.com/page.html', - } -}; +const BANNER_PREBID_MEDIATYPE = { + sizes: [[300, 50]] +} const singleBannerBidRequest = { bidder: 'smaato', @@ -205,385 +35,1171 @@ const singleBannerBidRequest = { adspaceId: 'adspaceId' }, mediaTypes: { - banner: { - sizes: [[300, 50]] - } - }, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: 'transactionId', - sizes: [[300, 50]], - bidId: 'bidId', - bidderRequestId: 'bidderRequestId', - auctionId: 'auctionId', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 -}; - -const singleVideoBidRequest = { - bidder: 'smaato', - params: { - publisherId: 'publisherId', - adspaceId: 'adspaceId' - }, - mediaTypes: { - video: { - context: 'outstream', - playerSize: [[768, 1024]], - mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], - minduration: 5, - maxduration: 30, - startdelay: 0, - linearity: 1, - protocols: [7], - skip: 1, - skipmin: 5, - api: [7], - ext: {rewarded: 0} - } - }, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: 'transactionId', - sizes: [[300, 50]], - bidId: 'bidId', - bidderRequestId: 'bidderRequestId', - auctionId: 'auctionId', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 -}; - -const combinedBannerAndVideoBidRequest = { - bidder: 'smaato', - params: { - publisherId: 'publisherId', - adspaceId: 'adspaceId' - }, - mediaTypes: { - banner: { - sizes: [[300, 50]] - }, - video: { - context: 'outstream', - playerSize: [[768, 1024]], - mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], - minduration: 5, - maxduration: 30, - startdelay: 0, - linearity: 1, - protocols: [7], - skip: 1, - skipmin: 5, - api: [7], - ext: {rewarded: 0} - } + banner: BANNER_PREBID_MEDIATYPE }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'transactionId', sizes: [[300, 50]], bidId: 'bidId', bidderRequestId: 'bidderRequestId', - auctionId: 'auctionId', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0 }; -const inAppBidRequest = { - bidder: 'smaato', - params: { - publisherId: 'publisherId', - adspaceId: 'adspaceId', - app: { - ifa: 'aDeviceId', - geo: { - lat: 33.3, - lon: -88.8 - } - } - }, - mediaTypes: { - banner: { - sizes: [[300, 50]] - } - }, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: 'transactionId', - sizes: [[300, 50]], - bidId: 'bidId', - bidderRequestId: 'bidderRequestId', - auctionId: 'auctionId', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 -}; - -const userIdBidRequest = { - bidder: 'smaato', - params: { - publisherId: 'publisherId', - adspaceId: 'adspaceId' - }, - mediaTypes: { - banner: { - sizes: [[300, 50]] - } - }, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: 'transactionId', - sizes: [[300, 50]], - bidId: 'bidId', - bidderRequestId: 'bidderRequestId', - auctionId: 'auctionId', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0, - userId: { - criteoId: '123456', - tdid: '89145' - }, - userIdAsEids: createEidsArray({ - criteoId: '123456', - tdid: '89145' - }) -}; +const extractPayloadOfFirstAndOnlyRequest = (reqs) => { + expect(reqs).to.have.length(1); + return JSON.parse(reqs[0].data); +} describe('smaatoBidAdapterTest', () => { describe('isBidRequestValid', () => { - it('has valid params', () => { - expect(spec.isBidRequestValid({params: {publisherId: '123', adspaceId: '456'}})).to.be.true; - expect(spec.isBidRequestValid(singleBannerBidRequest)).to.be.true; - }); - it('has invalid params', () => { + it('is invalid, when params object is not present', () => { expect(spec.isBidRequestValid({})).to.be.false; + }); + + it('is invalid, when params object is empty', () => { expect(spec.isBidRequestValid({params: {}})).to.be.false; - expect(spec.isBidRequestValid({params: {publisherId: '123'}})).to.be.false; - expect(spec.isBidRequestValid({params: {publisherId: '123', adspaceId: 456}})).to.be.false; }); - }); - describe('buildRequests', () => { - beforeEach(() => { - this.req = JSON.parse(spec.buildRequests([singleBannerBidRequest], defaultBidderRequest).data); - this.sandbox = sinon.sandbox.create(); + it('is invalid, when publisherId is present but of wrong type', () => { + expect(spec.isBidRequestValid({params: {publisherId: 123}})).to.be.false; }); - afterEach(() => { - this.sandbox.restore(); + describe('for ad pod / long form video requests', () => { + const ADPOD = {video: {context: 'adpod'}} + it('is invalid, when adbreakId is missing', () => { + expect(spec.isBidRequestValid({mediaTypes: ADPOD, params: {publisherId: '123'}})).to.be.false; + }); + + it('is invalid, when adbreakId is present but of wrong type', () => { + expect(spec.isBidRequestValid({mediaTypes: ADPOD, params: {publisherId: '123', adbreakId: 456}})).to.be.false; + }); + + it('is valid, when required params are present', () => { + expect(spec.isBidRequestValid({mediaTypes: ADPOD, params: {publisherId: '123', adbreakId: '456'}})).to.be.true; + }); + + it('is invalid, when forbidden adspaceId param is present', () => { + expect(spec.isBidRequestValid({ + mediaTypes: ADPOD, + params: {publisherId: '123', adbreakId: '456', adspaceId: '42'} + })).to.be.false; + }); }); - it('can override endpoint', () => { - const overridenEndpoint = 'https://prebid/bidder'; - let bidRequest = utils.deepClone(singleBannerBidRequest); - utils.deepSetValue(bidRequest, 'params.endpoint', overridenEndpoint); - const actualEndpoint = spec.buildRequests([bidRequest], defaultBidderRequest).url; - expect(actualEndpoint).to.equal(overridenEndpoint); + describe('for non adpod requests', () => { + it('is invalid, when adspaceId is missing', () => { + expect(spec.isBidRequestValid({params: {publisherId: '123'}})).to.be.false; + }); + + it('is invalid, when adspaceId is present but of wrong type', () => { + expect(spec.isBidRequestValid({params: {publisherId: '123', adspaceId: 456}})).to.be.false; + }); + + it('is valid, when required params are present for minimal request', () => { + expect(spec.isBidRequestValid({params: {publisherId: '123', adspaceId: '456'}})).to.be.true; + }); + + it('is invalid, when forbidden adbreakId param is present', () => { + expect(spec.isBidRequestValid({params: {publisherId: '123', adspaceId: '456', adbreakId: '42'}})).to.be.false; + }); }); + }); - it('sends correct imps', () => { - expect(this.req.imp).to.deep.equal([ + describe('buildRequests', () => { + const BANNER_OPENRTB_IMP = { + w: 300, + h: 50, + format: [ { - id: 'bidId', - banner: { - w: 300, - h: 50, - format: [ - { - h: 50, - w: 300 - } - ] - }, - tagid: 'adspaceId' + h: 50, + w: 300 } - ]) - }); + ] + } - it('sends correct site', () => { - expect(this.req.site.id).to.exist.and.to.be.a('string'); - expect(this.req.site.domain).to.exist.and.to.be.a('string'); - expect(this.req.site.page).to.exist.and.to.be.a('string'); - expect(this.req.site.ref).to.equal('http://example.com/page.html'); - expect(this.req.site.publisher.id).to.equal('publisherId'); - }) + describe('common', () => { + const MINIMAL_BIDDER_REQUEST = { + refererInfo: { + referer: REFERRER, + } + }; - it('sends gdpr applies if exists', () => { - expect(this.req.regs.ext.gdpr).to.equal(1); - expect(this.req.user.ext.consent).to.equal('HFIDUYFIUYIUYWIPOI87392DSU'); - }); + it('auction type is 1 (first price auction)', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); - it('sends no gdpr applies if no gdpr exists', () => { - let req_without_gdpr = JSON.parse(spec.buildRequests([singleBannerBidRequest], minimalBidderRequest).data); - expect(req_without_gdpr.regs.ext.gdpr).to.not.exist; - expect(req_without_gdpr.user.ext.consent).to.not.exist; - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.at).to.be.equal(1); + }) - it('sends usp if exists', () => { - expect(this.req.regs.ext.us_privacy).to.equal('uspConsentString'); - }); + it('currency is US dollar', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.cur).to.be.deep.equal(['USD']); + }) + + it('can override endpoint', () => { + const overridenEndpoint = 'https://prebid/bidder'; + const updatedBidRequest = utils.deepClone(singleBannerBidRequest); + utils.deepSetValue(updatedBidRequest, 'params.endpoint', overridenEndpoint); + + const reqs = spec.buildRequests([updatedBidRequest], defaultBidderRequest); + + expect(reqs).to.have.length(1); + expect(reqs[0].url).to.equal(overridenEndpoint); + }); + + it('sends correct imp', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp).to.deep.equal([ + { + id: 'bidId', + banner: BANNER_OPENRTB_IMP, + tagid: 'adspaceId', + } + ]); + }); + + it('sends bidfloor when configured', () => { + const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); + singleBannerBidRequestWithFloor.getFloor = function(arg) { + if (arg.currency === 'USD' && + arg.mediaType === 'banner' && + JSON.stringify(arg.size) === JSON.stringify([300, 50])) { + return { + currency: 'USD', + floor: 0.123 + } + } + } + const reqs = spec.buildRequests([singleBannerBidRequestWithFloor], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.equal(0.123); + }); + + it('bidfloor uses catch-all when multiple sizes', () => { + const singleBannerMultipleSizesBidRequestWithFloor = Object.assign({}, singleBannerBidRequest, { + mediaTypes: { + banner: { + sizes: [[320, 50], [320, 250]] + } + } + }); + singleBannerMultipleSizesBidRequestWithFloor.getFloor = function(arg) { + if (arg.size === '*') { + return { + currency: 'USD', + floor: 0.101 + } + } + } + const reqs = spec.buildRequests([singleBannerMultipleSizesBidRequestWithFloor], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.equal(0.101); + }); + + it('sends undefined bidfloor when not a function', () => { + const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); + singleBannerBidRequestWithFloor.getFloor = 0 + + const reqs = spec.buildRequests([singleBannerBidRequestWithFloor], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.undefined + }); + + it('sends undefined bidfloor when invalid', () => { + const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); + singleBannerBidRequestWithFloor.getFloor = function() { + return undefined; + } + const reqs = spec.buildRequests([singleBannerBidRequestWithFloor], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.undefined + }); + + it('sends undefined bidfloor when not a number', () => { + const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); + singleBannerBidRequestWithFloor.getFloor = function() { + return { + currency: 'USD', + } + } + const reqs = spec.buildRequests([singleBannerBidRequestWithFloor], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.undefined; + }); + + it('sends undefined bidfloor when wrong currency', () => { + const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); + singleBannerBidRequestWithFloor.getFloor = function() { + return { + currency: 'EUR', + floor: 0.123 + } + } + const reqs = spec.buildRequests([singleBannerBidRequestWithFloor], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.undefined; + }); + + it('sends correct site', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.id).to.exist.and.to.be.a('string'); + expect(req.site.domain).to.exist.and.to.be.a('string'); + expect(req.site.page).to.exist.and.to.be.a('string'); + expect(req.site.ref).to.equal(REFERRER); + expect(req.site.publisher.id).to.equal('publisherId'); + }) + + it('sends gdpr applies if exists', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.ext.gdpr).to.equal(1); + expect(req.user.ext.consent).to.equal(CONSENT_STRING); + }); + + it('sends no gdpr applies if no gdpr exists', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], MINIMAL_BIDDER_REQUEST); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.ext.gdpr).to.not.exist; + expect(req.user.ext.consent).to.not.exist; + }); + + it('sends us_privacy if exists', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.ext.us_privacy).to.equal('uspConsentString'); + }); + + it('sends no schain if no schain exists', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.source.ext.schain).to.not.exist; + }); + + it('sends instl if instl exists', () => { + const instl = { instl: 1 }; + const bidRequestWithInstl = Object.assign({}, singleBannerBidRequest, {ortb2Imp: instl}); + + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + }); + + it('sends tmax', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.tmax).to.equal(1200); + }); + + it('sends no us_privacy if no us_privacy exists', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], MINIMAL_BIDDER_REQUEST); - it('sends tmax', () => { - expect(this.req.tmax).to.equal(1200); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.ext.us_privacy).to.not.exist; + }); + + it('sends first party data', () => { + this.sandbox = sinon.sandbox.create() + this.sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + ortb2: { + site: { + keywords: 'power tools,drills' + }, + user: { + keywords: 'a,b', + gender: 'M', + yob: 1984 + } + } + }; + return utils.deepAccess(config, key); + }); + + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.user.gender).to.equal('M'); + expect(req.user.yob).to.equal(1984); + expect(req.user.keywords).to.eql('a,b'); + expect(req.user.ext.consent).to.equal(CONSENT_STRING); + expect(req.site.keywords).to.eql('power tools,drills'); + expect(req.site.publisher.id).to.equal('publisherId'); + this.sandbox.restore(); + }); + + it('has no user ids', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.user.ext.eids).to.not.exist; + }); }); - it('sends no usp if no usp exists', () => { - let req_without_usp = JSON.parse(spec.buildRequests([singleBannerBidRequest], minimalBidderRequest).data); - expect(req_without_usp.regs.ext.us_privacy).to.not.exist; + describe('multiple requests', () => { + it('build individual server request for each bid request', () => { + const bidRequest1 = utils.deepClone(singleBannerBidRequest); + const bidRequest1BidId = '1111'; + utils.deepSetValue(bidRequest1, 'bidId', bidRequest1BidId); + const bidRequest2 = utils.deepClone(singleBannerBidRequest); + const bidRequest2BidId = '2222'; + utils.deepSetValue(bidRequest2, 'bidId', bidRequest2BidId); + + const reqs = spec.buildRequests([bidRequest1, bidRequest2], defaultBidderRequest); + + expect(reqs).to.have.length(2); + expect(JSON.parse(reqs[0].data).imp[0].id).to.be.equal(bidRequest1BidId); + expect(JSON.parse(reqs[1].data).imp[0].id).to.be.equal(bidRequest2BidId); + }); }); - it('sends fp data', () => { - this.sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - ortb2: { - site, - user + describe('buildRequests for video imps', () => { + const VIDEO_OUTSTREAM_PREBID_MEDIATYPE = { + context: 'outstream', + playerSize: [[768, 1024]], + mimes: ['video/mp4', 'video/quicktime', 'video/3gpp', 'video/x-m4v'], + minduration: 5, + maxduration: 30, + startdelay: 0, + linearity: 1, + protocols: [7], + skip: 1, + skipmin: 5, + api: [7], + ext: {rewarded: 0} + }; + const VIDEO_OUTSTREAM_OPENRTB_IMP = { + mimes: ['video/mp4', 'video/quicktime', 'video/3gpp', 'video/x-m4v'], + minduration: 5, + startdelay: 0, + linearity: 1, + h: 1024, + maxduration: 30, + skip: 1, + protocols: [7], + ext: { + rewarded: 0 + }, + skipmin: 5, + api: [7], + w: 768 + }; + const singleVideoBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + video: VIDEO_OUTSTREAM_PREBID_MEDIATYPE + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }; + + it('sends correct video imps', () => { + const reqs = spec.buildRequests([singleVideoBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].id).to.be.equal('bidId'); + expect(req.imp[0].tagid).to.be.equal('adspaceId'); + expect(req.imp[0].bidfloor).to.be.undefined; + expect(req.imp[0].video).to.deep.equal(VIDEO_OUTSTREAM_OPENRTB_IMP); + }); + + it('sends bidfloor when configured', () => { + const singleVideoBidRequestWithFloor = Object.assign({}, singleVideoBidRequest); + singleVideoBidRequestWithFloor.getFloor = function(arg) { + if (arg.currency === 'USD' && + arg.mediaType === 'video' && + JSON.stringify(arg.size) === JSON.stringify([768, 1024])) { + return { + currency: 'USD', + floor: 0.456 + } } + } + const reqs = spec.buildRequests([singleVideoBidRequestWithFloor], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.equal(0.456); + }); + + it('sends instl if instl exists', () => { + const instl = { instl: 1 }; + const bidRequestWithInstl = Object.assign({}, singleVideoBidRequest, {ortb2Imp: instl}); + + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + }); + + it('splits multi format bid requests', () => { + const combinedBannerAndVideoBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + banner: BANNER_PREBID_MEDIATYPE, + video: VIDEO_OUTSTREAM_PREBID_MEDIATYPE + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 }; - return utils.deepAccess(config, key); - }); - let bidRequest = utils.deepClone(singleBannerBidRequest); - let req_fpd = JSON.parse(spec.buildRequests([bidRequest], defaultBidderRequest).data); - expect(req_fpd.user.gender).to.equal('M'); - expect(req_fpd.user.yob).to.equal(1984); - expect(req_fpd.user.keywords).to.eql('a,b'); - expect(req_fpd.user.ext.consent).to.equal('HFIDUYFIUYIUYWIPOI87392DSU'); - expect(req_fpd.site.keywords).to.eql('power tools,drills'); - expect(req_fpd.site.publisher.id).to.equal('publisherId'); - }); - it('has no user ids', () => { - expect(this.req.user.ext.eids).to.not.exist; - }); - }); + const reqs = spec.buildRequests([combinedBannerAndVideoBidRequest], defaultBidderRequest); - describe('buildRequests for video imps', () => { - it('sends correct video imps', () => { - let req = JSON.parse(spec.buildRequests([singleVideoBidRequest], defaultBidderRequest).data); - expect(req.imp).to.deep.equal([ - { - id: 'bidId', - video: { - mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], - minduration: 5, - startdelay: 0, - linearity: 1, - h: 1024, - maxduration: 30, - skip: 1, - protocols: [7], - ext: { - rewarded: 0 + expect(reqs).to.have.length(2); + expect(JSON.parse(reqs[0].data).imp[0].banner).to.deep.equal(BANNER_OPENRTB_IMP); + expect(JSON.parse(reqs[0].data).imp[0].video).to.not.exist; + expect(JSON.parse(reqs[1].data).imp[0].banner).to.not.exist; + expect(JSON.parse(reqs[1].data).imp[0].video).to.deep.equal(VIDEO_OUTSTREAM_OPENRTB_IMP); + }); + + describe('ad pod / long form video', () => { + describe('required parameters with requireExactDuration false', () => { + const ADBREAK_ID = 'adbreakId'; + const ADPOD = 'adpod'; + const BID_ID = '4331'; + const W = 640; + const H = 480; + const ADPOD_DURATION = 300; + const DURATION_RANGE = [15, 30]; + const longFormVideoBidRequest = { + params: { + publisherId: 'publisherId', + adbreakId: ADBREAK_ID, }, - skipmin: 5, - api: [7], - w: 768 - }, - tagid: 'adspaceId' - } - ]) - }); - it('allows combines banner and video imp in single bid request', () => { - let req = JSON.parse(spec.buildRequests([combinedBannerAndVideoBidRequest], defaultBidderRequest).data); - expect(req.imp).to.deep.equal([ - { - id: 'bidId', - banner: { - w: 300, - h: 50, - format: [ - { - h: 50, - w: 300 + mediaTypes: { + video: { + context: ADPOD, + playerSize: [[W, H]], + adPodDurationSec: ADPOD_DURATION, + durationRangeSec: DURATION_RANGE, + requireExactDuration: false } - ] - }, - video: { - mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], - minduration: 5, - startdelay: 0, - linearity: 1, - h: 1024, - maxduration: 30, - skip: 1, - protocols: [7], - ext: { - rewarded: 0 }, - skipmin: 5, - api: [7], - w: 768 - }, - tagid: 'adspaceId' - } - ]) - }); - it('allows two imps in the same bid request', () => { - let req = JSON.parse(spec.buildRequests([singleBannerBidRequest, singleBannerBidRequest], defaultBidderRequest).data); - expect(req.imp).to.have.length(2); - }); - }); + bidId: BID_ID + }; + + it('sends required fields', () => { + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.id).to.be.equal(AUCTION_ID); + expect(req.imp.length).to.be.equal(ADPOD_DURATION / DURATION_RANGE[0]); + expect(req.imp[0].id).to.be.equal(BID_ID); + expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[0].bidfloor).to.be.undefined; + expect(req.imp[0].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[0].video.w).to.be.equal(W); + expect(req.imp[0].video.h).to.be.equal(H); + expect(req.imp[0].video.maxduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[0].video.sequence).to.be.equal(1); + expect(req.imp[1].id).to.be.equal(BID_ID); + expect(req.imp[1].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[1].bidfloor).to.be.undefined; + expect(req.imp[1].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[1].video.w).to.be.equal(W); + expect(req.imp[1].video.h).to.be.equal(H); + expect(req.imp[1].video.maxduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[1].video.sequence).to.be.equal(2); + }); + + it('sends instl if instl exists', () => { + const instl = { instl: 1 }; + const bidRequestWithInstl = Object.assign({}, longFormVideoBidRequest, {ortb2Imp: instl}); + + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + expect(req.imp[1].instl).to.equal(1); + }); + + it('sends bidfloor when configured', () => { + const longFormVideoBidRequestWithFloor = Object.assign({}, longFormVideoBidRequest); + longFormVideoBidRequestWithFloor.getFloor = function(arg) { + if (arg.currency === 'USD' && + arg.mediaType === 'video' && + JSON.stringify(arg.size) === JSON.stringify([640, 480])) { + return { + currency: 'USD', + floor: 0.789 + } + } + } + const reqs = spec.buildRequests([longFormVideoBidRequestWithFloor], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.equal(0.789); + expect(req.imp[1].bidfloor).to.be.equal(0.789); + }); + + it('sends brand category exclusion as true when config is set to true', () => { + config.setConfig({adpod: {brandCategoryExclusion: true}}); + + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(true); + }); + + it('sends brand category exclusion as false when config is set to false', () => { + config.setConfig({adpod: {brandCategoryExclusion: false}}); + + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(false); + }); + + it('sends brand category exclusion as false when config is not set', () => { + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(false); + }); + }); + describe('required parameters with requireExactDuration true', () => { + const ADBREAK_ID = 'adbreakId'; + const ADPOD = 'adpod'; + const BID_ID = '4331'; + const W = 640; + const H = 480; + const ADPOD_DURATION = 5; + const DURATION_RANGE = [5, 15, 25]; + const longFormVideoBidRequest = { + params: { + publisherId: 'publisherId', + adbreakId: ADBREAK_ID, + }, + mediaTypes: { + video: { + context: ADPOD, + playerSize: [[W, H]], + adPodDurationSec: ADPOD_DURATION, + durationRangeSec: DURATION_RANGE, + requireExactDuration: true + } + }, + bidId: BID_ID + }; + + it('sends required fields', () => { + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.id).to.be.equal(AUCTION_ID); + expect(req.imp.length).to.be.equal(DURATION_RANGE.length); + expect(req.imp[0].id).to.be.equal(BID_ID); + expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[0].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[0].video.w).to.be.equal(W); + expect(req.imp[0].video.h).to.be.equal(H); + expect(req.imp[0].video.minduration).to.be.equal(DURATION_RANGE[0]); + expect(req.imp[0].video.maxduration).to.be.equal(DURATION_RANGE[0]); + expect(req.imp[0].video.sequence).to.be.equal(1); + expect(req.imp[1].id).to.be.equal(BID_ID); + expect(req.imp[1].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[1].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[1].video.w).to.be.equal(W); + expect(req.imp[1].video.h).to.be.equal(H); + expect(req.imp[1].video.minduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[1].video.maxduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[1].video.sequence).to.be.equal(2); + expect(req.imp[2].id).to.be.equal(BID_ID); + expect(req.imp[2].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[2].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[2].video.w).to.be.equal(W); + expect(req.imp[2].video.h).to.be.equal(H); + expect(req.imp[2].video.minduration).to.be.equal(DURATION_RANGE[2]); + expect(req.imp[2].video.maxduration).to.be.equal(DURATION_RANGE[2]); + expect(req.imp[2].video.sequence).to.be.equal(3); + }); + }); + + describe('forwarding of optional parameters', () => { + const MIMES = ['video/mp4', 'video/quicktime', 'video/3gpp', 'video/x-m4v']; + const STARTDELAY = 0; + const LINEARITY = 1; + const SKIP = 1; + const PROTOCOLS = [7]; + const SKIPMIN = 5; + const API = [7]; + const validBasicAdpodBidRequest = { + params: { + publisherId: 'publisherId', + adbreakId: 'adbreakId', + }, + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + mimes: MIMES, + startdelay: STARTDELAY, + linearity: LINEARITY, + skip: SKIP, + protocols: PROTOCOLS, + skipmin: SKIPMIN, + api: API + } + }, + bidId: 'bidId' + }; + + it('sends general video fields when they are present', () => { + const reqs = spec.buildRequests([validBasicAdpodBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.mimes).to.eql(MIMES); + expect(req.imp[0].video.startdelay).to.be.equal(STARTDELAY); + expect(req.imp[0].video.linearity).to.be.equal(LINEARITY); + expect(req.imp[0].video.skip).to.be.equal(SKIP); + expect(req.imp[0].video.protocols).to.eql(PROTOCOLS); + expect(req.imp[0].video.skipmin).to.be.equal(SKIPMIN); + expect(req.imp[0].video.api).to.eql(API); + }); + + it('sends series name when parameter is present', () => { + const SERIES_NAME = 'foo' + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvSeriesName = SERIES_NAME; + + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.series).to.be.equal(SERIES_NAME); + }); + + it('sends episode name when parameter is present', () => { + const EPISODE_NAME = 'foo' + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvEpisodeName = EPISODE_NAME; - describe('in-app requests', () => { - it('add geo and ifa info to device object', () => { - let req = JSON.parse(spec.buildRequests([inAppBidRequest], defaultBidderRequest).data); - expect(req.device.geo).to.deep.equal({'lat': 33.3, 'lon': -88.8}); - expect(req.device.ifa).to.equal('aDeviceId'); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.title).to.be.equal(EPISODE_NAME); + }); + + it('sends season number as string when parameter is present', () => { + const SEASON_NUMBER_AS_NUMBER_IN_PREBID_REQUEST = 42 + const SEASON_NUMBER_AS_STRING_IN_OUTGOING_REQUEST = '42' + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvSeasonNumber = SEASON_NUMBER_AS_NUMBER_IN_PREBID_REQUEST; + + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.season).to.be.equal(SEASON_NUMBER_AS_STRING_IN_OUTGOING_REQUEST); + }); + + it('sends episode number when parameter is present', () => { + const EPISODE_NUMBER = 42 + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvEpisodeNumber = EPISODE_NUMBER; + + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.episode).to.be.equal(EPISODE_NUMBER); + }); + + it('sends content length when parameter is present', () => { + const LENGTH = 42 + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.contentLengthSec = LENGTH; + + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.len).to.be.equal(LENGTH); + }); + + it('sends livestream as 1 when content mode parameter is live', () => { + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.contentMode = 'live'; + + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.livestream).to.be.equal(1); + }); + + it('sends livestream as 0 when content mode parameter is on-demand', () => { + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.contentMode = 'on-demand'; + + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.livestream).to.be.equal(0); + }); + + it("doesn't send any optional parameters when none are present", () => { + const reqs = spec.buildRequests([validBasicAdpodBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.requireExactDuration).to.not.exist; + expect(req.site.content).to.not.exist; + }); + }); + }); }); - it('add only ifa to device object', () => { - let inAppBidRequestWithoutGeo = utils.deepClone(inAppBidRequest); - delete inAppBidRequestWithoutGeo.params.app.geo - let req = JSON.parse(spec.buildRequests([inAppBidRequestWithoutGeo], defaultBidderRequest).data); - expect(req.device.geo).to.not.exist; - expect(req.device.ifa).to.equal('aDeviceId'); + describe('in-app requests', () => { + const LOCATION = { + lat: 33.3, + lon: -88.8 + } + const DEVICE_ID = 'aDeviceId' + const inAppBidRequestWithoutAppParams = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + banner: BANNER_PREBID_MEDIATYPE + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }; + + it('when geo and ifa info present, then add both to device object', () => { + const inAppBidRequest = utils.deepClone(inAppBidRequestWithoutAppParams); + inAppBidRequest.params.app = {ifa: DEVICE_ID, geo: LOCATION}; + + const reqs = spec.buildRequests([inAppBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.device.geo).to.deep.equal(LOCATION); + expect(req.device.ifa).to.equal(DEVICE_ID); + }); + + it('when ifa is present but geo is missing, then add only ifa to device object', () => { + const inAppBidRequest = utils.deepClone(inAppBidRequestWithoutAppParams); + inAppBidRequest.params.app = {ifa: DEVICE_ID}; + + const reqs = spec.buildRequests([inAppBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.device.geo).to.not.exist; + expect(req.device.ifa).to.equal(DEVICE_ID); + }); + + it('when geo is present but ifa is missing, then add only geo to device object', () => { + const inAppBidRequest = utils.deepClone(inAppBidRequestWithoutAppParams); + inAppBidRequest.params.app = {geo: LOCATION}; + + const reqs = spec.buildRequests([inAppBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.device.geo).to.deep.equal(LOCATION); + expect(req.device.ifa).to.not.exist; + }); + + it('when app param does not exist, then add no specific device info', () => { + const reqs = spec.buildRequests([inAppBidRequestWithoutAppParams], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.device.geo).to.not.exist; + expect(req.device.ifa).to.not.exist; + }); }); - it('add no specific device info if param does not exist', () => { - let req = JSON.parse(spec.buildRequests([singleBannerBidRequest], defaultBidderRequest).data); - expect(req.device.geo).to.not.exist; - expect(req.device.ifa).to.not.exist; + + describe('user ids in requests', () => { + it('user ids are added to user.ext.eids', () => { + const userIdBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + banner: BANNER_PREBID_MEDIATYPE + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + userId: { + criteoId: '123456', + tdid: '89145' + }, + userIdAsEids: createEidsArray({ + criteoId: '123456', + tdid: '89145' + }) + }; + + const reqs = spec.buildRequests([userIdBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.user.ext.eids).to.exist; + expect(req.user.ext.eids).to.have.length(2); + }); }); - }); - describe('user ids in requests', () => { - it('user ids are added to user.ext.eids', () => { - let req = JSON.parse(spec.buildRequests([userIdBidRequest], defaultBidderRequest).data); - expect(req.user.ext.eids).to.exist; - expect(req.user.ext.eids).to.have.length(2); + describe('schain in request', () => { + it('schain is added to source.ext.schain', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'asi', + 'sid': 'sid', + 'rid': 'rid', + 'hp': 1 + } + ] + }; + const bidRequestWithSchain = Object.assign({}, singleBannerBidRequest, {schain: schain}); + + const reqs = spec.buildRequests([bidRequestWithSchain], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.source.ext.schain).to.deep.equal(schain); + }); }); }); describe('interpretResponse', () => { - it('single image reponse', () => { - const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_IMG), request); - assert.deepStrictEqual(bids, interpretedBidsImg); - }); - it('single richmedia reponse', () => { - const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_RICHMEDIA), request); - assert.deepStrictEqual(bids, interpretedBidsRichmedia); + function buildBidRequest(payloadAsJsObj = {imp: [{}]}) { + return { + method: 'POST', + url: 'https://prebid.ad.smaato.net/oapi/prebid', + data: JSON.stringify(payloadAsJsObj) + } + } + + const buildOpenRtbBidResponse = (adType) => { + let adm = ''; + + switch (adType) { + case ADTYPE_IMG: + adm = JSON.stringify({ + image: { + img: { + url: 'https://prebid/static/ad.jpg', + w: 320, + h: 50, + ctaurl: 'https://prebid/track/ctaurl' + }, + impressiontrackers: [ + 'https://prebid/track/imp/1', + 'https://prebid/track/imp/2' + ], + clicktrackers: [ + 'https://prebid/track/click/1' + ] + } + }); + break; + case ADTYPE_RICHMEDIA: + adm = JSON.stringify({ + richmedia: { + mediadata: { + content: '

RICHMEDIA CONTENT

', + w: 800, + h: 600 + }, + impressiontrackers: [ + 'https://prebid/track/imp/1', + 'https://prebid/track/imp/2' + ], + clicktrackers: [ + 'https://prebid/track/click/1' + ] + } + }); + break; + case ADTYPE_VIDEO: + adm = ''; + break; + default: + throw Error('Invalid AdType'); + } + + return { + body: { + bidid: '04db8629-179d-4bcd-acce-e54722969006', + cur: 'USD', + ext: {}, + id: '5ebea288-f13a-4754-be6d-4ade66c68877', + seatbid: [ + { + bid: [ + { + 'adm': adm, + 'adomain': [ + 'smaato.com' + ], + 'bidderName': 'smaato', + 'cid': 'CM6523', + 'crid': 'CR69381', + 'dealid': '12345', + 'id': '6906aae8-7f74-4edd-9a4f-f49379a3cadd', + 'impid': '226416e6e6bf41', + 'iurl': 'https://prebid/iurl', + 'nurl': 'https://prebid/nurl', + 'price': 0.01, + 'w': 350, + 'h': 50 + } + ], + seat: 'CM6523' + } + ], + }, + headers: { + get: function (header) { + if (header === 'X-SMT-ADTYPE') { + return adType; + } + } + } + }; + }; + + it('returns empty array on no bid responses', () => { + const response_with_empty_body = {body: {}}; + + const bids = spec.interpretResponse(response_with_empty_body, buildBidRequest()); + + expect(bids).to.be.empty; }); - it('single video reponse', () => { - const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_VIDEO), request); - assert.deepStrictEqual(bids, interpretedBidsVideo); + + describe('non ad pod', () => { + it('single image reponse', () => { + const bids = spec.interpretResponse(buildOpenRtbBidResponse(ADTYPE_IMG), buildBidRequest()); + + expect(bids).to.deep.equal([ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + ad: '
', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + mediaType: 'banner', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'banner' + } + } + ]); + }); + + it('single richmedia reponse', () => { + const bids = spec.interpretResponse(buildOpenRtbBidResponse(ADTYPE_RICHMEDIA), buildBidRequest()); + + expect(bids).to.deep.equal([ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + ad: '

RICHMEDIA CONTENT

', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + mediaType: 'banner', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'banner' + } + } + ]); + }); + + it('single video response', () => { + const bids = spec.interpretResponse(buildOpenRtbBidResponse(ADTYPE_VIDEO), buildBidRequest()); + + expect(bids).to.deep.equal([ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + vastXml: '', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + mediaType: 'video', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'video' + } + } + ]); + }); + + it('ignores bid response with invalid ad type', () => { + const serverResponse = buildOpenRtbBidResponse(ADTYPE_IMG); + serverResponse.headers.get = (header) => { + if (header === 'X-SMT-ADTYPE') { + return undefined; + } + }; + + const bids = spec.interpretResponse(serverResponse, buildBidRequest()); + + expect(bids).to.be.empty; + }); }); - it('ignores bid response with invalid ad type', () => { - let resp = openRtbBidResponse(ADTYPE_IMG); - resp.headers.get = (header) => { - if (header === 'X-SMT-ADTYPE') { - return undefined; - } - } - const bids = spec.interpretResponse(resp, request); - expect(bids).to.be.empty + + describe('ad pod', () => { + const bidRequestWithAdpodContext = buildBidRequest({imp: [{video: {ext: {context: 'adpod'}}}]}); + const PRIMARY_CAT_ID = 1337 + const serverResponse = { + body: { + bidid: '04db8629-179d-4bcd-acce-e54722969006', + cur: 'USD', + ext: {}, + id: '5ebea288-f13a-4754-be6d-4ade66c68877', + seatbid: [ + { + bid: [ + { + adm: '', + adomain: [ + 'smaato.com' + ], + bidderName: 'smaato', + cid: 'CM6523', + crid: 'CR69381', + dealid: '12345', + id: '6906aae8-7f74-4edd-9a4f-f49379a3cadd', + impid: '226416e6e6bf41', + iurl: 'https://prebid/iurl', + nurl: 'https://prebid/nurl', + price: 0.01, + w: 350, + h: 50, + cat: [PRIMARY_CAT_ID], + ext: { + duration: 42 + } + } + ], + seat: 'CM6523' + } + ] + }, + headers: {get: () => undefined} + }; + + it('sets required values for adpod bid from server response', () => { + const bids = spec.interpretResponse(serverResponse, bidRequestWithAdpodContext); + + expect(bids).to.deep.equal([ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + vastXml: '', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + mediaType: 'video', + video: { + context: 'adpod', + durationSeconds: 42 + }, + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'video' + } + } + ]); + }); + + it('sets primary category id in case of enabled brand category exclusion', () => { + config.setConfig({adpod: {brandCategoryExclusion: true}}); + + const bids = spec.interpretResponse(serverResponse, bidRequestWithAdpodContext) + + expect(bids[0].meta.primaryCatId).to.be.equal(PRIMARY_CAT_ID) + }) }); + it('uses correct TTL when expire header exists', () => { const clock = sinon.useFakeTimers(); clock.tick(2000); - let resp = openRtbBidResponse(ADTYPE_IMG); + const resp = buildOpenRtbBidResponse(ADTYPE_IMG); resp.headers.get = (header) => { if (header === 'X-SMT-ADTYPE') { return ADTYPE_IMG; @@ -591,16 +1207,28 @@ describe('smaatoBidAdapterTest', () => { if (header === 'X-SMT-Expires') { return 2000 + (400 * 1000); } - } - const bids = spec.interpretResponse(resp, request); + }; + + const bids = spec.interpretResponse(resp, buildBidRequest()); + expect(bids[0].ttl).to.equal(400); + clock.restore(); }); + it('uses net revenue flag send from server', () => { - let resp = openRtbBidResponse(ADTYPE_IMG); + const resp = buildOpenRtbBidResponse(ADTYPE_IMG); resp.body.seatbid[0].bid[0].ext = {net: false}; - const bids = spec.interpretResponse(resp, request); + + const bids = spec.interpretResponse(resp, buildBidRequest()); + expect(bids[0].netRevenue).to.equal(false); - }) + }); }); + + describe('getUserSyncs', () => { + it('returns no pixels', () => { + expect(spec.getUserSyncs()).to.be.empty + }) + }) }); diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 749de43b9af..727a7d7c1d6 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -1,17 +1,6 @@ -import { - expect -} from 'chai'; -import { - spec -} from 'modules/smartadserverBidAdapter.js'; -import { - newBidder -} from 'src/adapters/bidderFactory.js'; -import { - config -} from 'src/config.js'; -import * as utils from 'src/utils.js'; -import { requestBidsHook } from 'modules/consentManagement.js'; +import { expect } from 'chai'; +import { config } from 'src/config.js'; +import { spec } from 'modules/smartadserverBidAdapter.js'; // Default params with optional ones describe('Smart bid adapter tests', function () { @@ -153,6 +142,22 @@ describe('Smart bid adapter tests', function () { } }; + var BID_RESPONSE_IFRAME_SYNC_MISSING_CSYNC = { + body: { + cpm: 12, + width: 300, + height: 250, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake.url', + ad: '< --- awesome script --- >', + cSyncUrl: null, + isNoAd: false + } + }; + it('Verify build request', function () { config.setConfig({ 'currency': { @@ -302,6 +307,11 @@ describe('Smart bid adapter tests', function () { iframeEnabled: true }, []); expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE_IFRAME_SYNC_MISSING_CSYNC]); + expect(syncs).to.have.lengthOf(0); }); it('Verifies user sync using dspPixels', function () { @@ -539,6 +549,130 @@ describe('Smart bid adapter tests', function () { expect(request[0]).to.be.empty; expect(request[1]).to.not.be.empty; }); + + describe('Instream videoData meta & params tests', function () { + it('Verify videoData assigns values from meta', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests([{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], // It seems prebid.js transforms the player size array into an array of array... + protocols: [8, 2], + startdelay: 0 + } + }, + params: { + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid', + bidfloor: 0.420, + buId: '7569', + appName: 'Mozilla', + ckId: 42, + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]); + + expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(8); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(1); + }); + + it('Verify videoData default values assigned', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests([{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]] // It seems prebid.js transforms the player size array into an array of array... + } + }, + params: { + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid', + bidfloor: 0.420, + buId: '7569', + appName: 'Mozilla', + ckId: 42, + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]); + + expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(null); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(2); + }); + + it('Verify videoData params override meta values', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests([{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], // It seems prebid.js transforms the player size array into an array of array... + protocols: [8, 2], + startdelay: 0 + } + }, + params: { + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid', + bidfloor: 0.420, + buId: '7569', + appName: 'Mozilla', + ckId: 42, + video: { + protocol: 6, + startDelay: 3 + } + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]); + + expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(3); + }); + }); }); describe('Outstream video tests', function () { @@ -644,6 +778,130 @@ describe('Smart bid adapter tests', function () { }); }); + describe('Outstream videoData meta & params tests', function () { + it('Verify videoData assigns values from meta', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests([{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]], // It seems prebid.js transforms the player size array into an array of array... + protocols: [8, 2], + startdelay: 0 + } + }, + params: { + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid-outstream', + bidfloor: 0.420, + buId: '7569', + appName: 'Mozilla', + ckId: 42, + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]); + + expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(8); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(1); + }); + + it('Verify videoData default values assigned', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests([{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]] // It seems prebid.js transforms the player size array into an array of array... + } + }, + params: { + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid-outstream', + bidfloor: 0.420, + buId: '7569', + appName: 'Mozilla', + ckId: 42, + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]); + + expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(null); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(2); + }); + + it('Verify videoData params override meta values', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests([{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]], // It seems prebid.js transforms the player size array into an array of array... + protocols: [8, 2], + startdelay: 0 + } + }, + params: { + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid-outstream', + bidfloor: 0.420, + buId: '7569', + appName: 'Mozilla', + ckId: 42, + video: { + protocol: 6, + startDelay: 3 + } + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]); + + expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(3); + }); + }); + describe('External ids tests', function () { it('Verify external ids in request and ids found', function () { config.setConfig({ @@ -715,4 +973,117 @@ describe('Smart bid adapter tests', function () { expect(null, actual); }); }); + + describe('Floors module', function () { + it('should include floor from bid params', function() { + const bidRequest = JSON.parse((spec.buildRequests(DEFAULT_PARAMS))[0].data); + expect(bidRequest.bidfloor).to.deep.equal(DEFAULT_PARAMS[0].params.bidfloor); + }); + + it('should return floor from module', function() { + const moduleFloor = 1.5; + const bidRequest = JSON.parse((spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL))[0].data); + bidRequest.getFloor = function () { + return { floor: moduleFloor }; + }; + + const floor = spec.getBidFloor(bidRequest, 'EUR'); + expect(floor).to.deep.equal(moduleFloor); + }); + + it('should return default floor when module not activated', function() { + const bidRequest = JSON.parse((spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL))[0].data); + + const floor = spec.getBidFloor(bidRequest, 'EUR'); + expect(floor).to.deep.equal(0); + }); + + it('should return default floor when getFloor returns not proper object', function() { + const bidRequest = JSON.parse((spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL))[0].data); + bidRequest.getFloor = function () { + return { floor: 'one' }; + }; + + const floor = spec.getBidFloor(bidRequest, 'EUR'); + expect(floor).to.deep.equal(0.0); + }); + + it('should return default floor when currency unknown', function() { + const bidRequest = JSON.parse((spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL))[0].data); + + const floor = spec.getBidFloor(bidRequest, null); + expect(floor).to.deep.equal(0); + }); + }); + + describe('Verify bid requests with multiple mediaTypes', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + var DEFAULT_PARAMS_MULTIPLE_MEDIA_TYPES = [{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + }, + video: { + context: 'outstream', + playerSize: [[640, 480]] // It seems prebid.js transforms the player size array into an array of array... + } + }, + bidder: 'smartadserver', + params: { + domain: 'https://prg.smartadserver.com', + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid', + bidfloor: 0.420, + buId: '7569', + appName: 'Mozilla', + ckId: 42, + video: { + protocol: 6, + startDelay: 1 + } + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]; + + it('Verify build request with banner and outstream', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const requests = spec.buildRequests(DEFAULT_PARAMS_MULTIPLE_MEDIA_TYPES); + expect(requests).to.have.lengthOf(2); + + const requestContents = requests.map(r => JSON.parse(r.data)); + const videoRequest = requestContents.filter(r => r.videoData)[0]; + expect(videoRequest).to.not.equal(null).and.to.not.be.undefined; + + const bannerRequest = requestContents.filter(r => !r.videoData)[0]; + expect(bannerRequest).to.not.equal(null).and.to.not.be.undefined; + + expect(videoRequest).to.not.equal(bannerRequest); + expect(videoRequest.videoData).to.have.property('videoProtocol').and.to.equal(6); + expect(videoRequest.videoData).to.have.property('playerWidth').and.to.equal(640); + expect(videoRequest.videoData).to.have.property('playerHeight').and.to.equal(480); + expect(videoRequest).to.have.property('siteid').and.to.equal('1234'); + expect(videoRequest).to.have.property('pageid').and.to.equal('5678'); + expect(videoRequest).to.have.property('formatid').and.to.equal('90'); + + expect(bannerRequest).to.have.property('siteid').and.to.equal('1234'); + expect(bannerRequest).to.have.property('pageid').and.to.equal('5678'); + expect(bannerRequest).to.have.property('formatid').and.to.equal('90'); + }); + }); }); diff --git a/test/spec/modules/smarticoBidAdapter_spec.js b/test/spec/modules/smarticoBidAdapter_spec.js index e8ca5b0e127..104fa22a851 100644 --- a/test/spec/modules/smarticoBidAdapter_spec.js +++ b/test/spec/modules/smarticoBidAdapter_spec.js @@ -71,24 +71,35 @@ describe('smarticoBidAdapter', function () { placementId: 'testPlacementId', }] }; - let serverResponse = [{ - bidId: '22499d052045', - id: 987654, - cpm: 10, - ttl: 30, - bannerFormatWidth: 300, - bannerFormatHeight: 250, - bannerFormatAlias: 'medium_rectangle' - }]; + let serverResponse = { + body: [{ + bidId: '22499d052045', + id: 987654, + cpm: 10, + netRevenue: 0, + currency: 'EUR', + ttl: 30, + bannerFormatWidth: 300, + bannerFormatHeight: 250, + bannerFormatAlias: 'medium_rectangle', + domains: ['www.advertiser.com'], + title: 'Advertiser' + }] + }; let expectedResponse = [{ requestId: bid.bidId, cpm: 10, width: 300, height: 250, creativeId: 987654, + currency: 'EUR', netRevenue: false, // gross ttl: 30, - ad: '') + expect(bid.ad).to.include('initPlayer') + }); +}); diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 3e1c3e78903..75ed7452b57 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -1,12 +1,24 @@ import {expect} from 'chai'; -import {spec} from 'modules/teadsBidAdapter.js'; +import {spec, storage} from 'modules/teadsBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import {getStorageManager} from 'src/storageManager'; const ENDPOINT = 'https://a.teads.tv/hb/bid-request'; const AD_SCRIPT = '"'; describe('teadsBidAdapter', () => { const adapter = newBidder(spec); + let cookiesAreEnabledStub, getCookieStub; + + beforeEach(function () { + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + getCookieStub = sinon.stub(storage, 'getCookie'); + }); + + afterEach(function () { + cookiesAreEnabledStub.restore(); + getCookieStub.restore(); + }); describe('inherited functions', () => { it('exists and is a function', () => { @@ -102,7 +114,7 @@ describe('teadsBidAdapter', () => { 'timeout': 3000 }; - it('sends bid request to ENDPOINT via POST', function() { + it('should send bid request to ENDPOINT via POST', function() { const request = spec.buildRequests(bidRequests, bidderResquestDefault); expect(request.url).to.equal(ENDPOINT); @@ -274,7 +286,6 @@ describe('teadsBidAdapter', () => { }); it('should send GDPR to endpoint with 22 status', function() { - let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; let bidderRequest = { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', @@ -322,7 +333,6 @@ describe('teadsBidAdapter', () => { }); it('should send GDPR to endpoint with 0 status when gdprApplies = false (vendorData = undefined)', function() { - let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; let bidderRequest = { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', @@ -377,7 +387,7 @@ describe('teadsBidAdapter', () => { } } }; - checkMediaTypesSizes(mediaTypesPlayerSize, '32x34') + checkMediaTypesSizes(mediaTypesPlayerSize, '32x34'); }); it('should add schain info to payload if available', function () { @@ -416,7 +426,7 @@ describe('teadsBidAdapter', () => { } } }; - checkMediaTypesSizes(mediaTypesVideoSizes, '12x14') + checkMediaTypesSizes(mediaTypesVideoSizes, '12x14'); }); it('should use good mediaTypes banner sizes', function() { @@ -427,7 +437,7 @@ describe('teadsBidAdapter', () => { } } }; - checkMediaTypesSizes(mediaTypesBannerSize, '46x48') + checkMediaTypesSizes(mediaTypesBannerSize, '46x48'); }); it('should use good mediaTypes for both video and banner sizes', function() { @@ -441,7 +451,222 @@ describe('teadsBidAdapter', () => { } } }; - checkMediaTypesSizes(hybridMediaTypes, ['46x48', '50x34', '45x45']) + checkMediaTypesSizes(hybridMediaTypes, ['46x48', '50x34', '45x45']); + }); + + describe('User IDs', function () { + const baseBidRequest = { + 'bidder': 'teads', + 'params': { + 'placementId': 10433394, + 'pageId': 1234 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + 'deviceWidth': 1680 + }; + + describe('FLoC ID', function () { + it('should not add cohortId and cohortVersion params to payload if FLoC ID system is not enabled', function () { + const bidRequest = { + ...baseBidRequest, + userId: {} // no "flocId" property -> assumption that the FLoC ID system is disabled + }; + + const request = spec.buildRequests([bidRequest], bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload).not.to.have.property('cohortId'); + expect(payload).not.to.have.property('cohortVersion'); + }); + + it('should add cohortId param to payload if FLoC ID system is enabled and ID available, but not version', function () { + const bidRequest = { + ...baseBidRequest, + userId: { + flocId: { + id: 'my-floc-id' + } + } + }; + + const request = spec.buildRequests([bidRequest], bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload.cohortId).to.equal('my-floc-id'); + expect(payload).not.to.have.property('cohortVersion'); + }); + + it('should add cohortId and cohortVersion params to payload if FLoC ID system is enabled', function () { + const bidRequest = { + ...baseBidRequest, + userId: { + flocId: { + id: 'my-floc-id', + version: 'chrome.1.1' + } + } + }; + + const request = spec.buildRequests([bidRequest], bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload.cohortId).to.equal('my-floc-id'); + expect(payload.cohortVersion).to.equal('chrome.1.1'); + }); + }); + + describe('Unified ID v2', function () { + it('should not add unifiedId2 param to payload if uid2 system is not enabled', function () { + const bidRequest = { + ...baseBidRequest, + userId: {} // no "uid2" property -> assumption that the Unified ID v2 system is disabled + }; + + const request = spec.buildRequests([bidRequest], bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload).not.to.have.property('unifiedId2'); + }); + + it('should add unifiedId2 param to payload if uid2 system is enabled', function () { + const bidRequest = { + ...baseBidRequest, + userId: { + uid2: { + id: 'my-unified-id-2' + } + } + }; + + const request = spec.buildRequests([bidRequest], bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload.unifiedId2).to.equal('my-unified-id-2'); + }) + }); + + describe('First-party cookie Teads ID', function () { + it('should not add firstPartyCookieTeadsId param to payload if cookies are not enabled', function () { + cookiesAreEnabledStub.returns(false); + + const request = spec.buildRequests([baseBidRequest], bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload).not.to.have.property('firstPartyCookieTeadsId'); + }); + + it('should not add firstPartyCookieTeadsId param to payload if first-party cookie is not available', function () { + cookiesAreEnabledStub.returns(true); + getCookieStub.withArgs('_tfpvi').returns(undefined); + + const request = spec.buildRequests([baseBidRequest], bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload).not.to.have.property('firstPartyCookieTeadsId'); + }); + + it('should add firstPartyCookieTeadsId param to payload if first-party cookie is available', function () { + cookiesAreEnabledStub.returns(true); + getCookieStub.withArgs('_tfpvi').returns('my-teads-id'); + + const request = spec.buildRequests([baseBidRequest], bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload.firstPartyCookieTeadsId).to.equal('my-teads-id'); + }); + }); + }); + + describe('Global Placement Id', function () { + let bidRequests = [ + { + 'bidder': 'teads', + 'params': { + 'placementId': 10433394, + 'pageId': 1234 + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + 'deviceWidth': 1680 + }, + { + 'bidder': 'teads', + 'params': { + 'placementId': 10433395, + 'pageId': 1234 + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1f', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ef', + 'deviceWidth': 1680 + } + ]; + + it('should add gpid if ortb2Imp.ext.gpid is present and is non empty', function () { + const updatedBidRequests = bidRequests.map(function(bidRequest, index) { + return { + ...bidRequest, + ortb2Imp: { + ext: { + gpid: '1111/home-left-' + index + } + } + }; + } + ); + const request = spec.buildRequests(updatedBidRequests, bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload.data[0].gpid).to.equal('1111/home-left-0'); + expect(payload.data[1].gpid).to.equal('1111/home-left-1'); + }); + + it('should not add gpid if ortb2Imp.ext.gpid is present but empty', function () { + const updatedBidRequests = bidRequests.map(bidRequest => ({ + ...bidRequest, + ortb2Imp: { + ext: { + gpid: '' + } + } + })); + + const request = spec.buildRequests(updatedBidRequests, bidderResquestDefault); + const payload = JSON.parse(request.data); + + return payload.data.forEach(bid => { + expect(bid).not.to.have.property('gpid'); + }); + }); + + it('should not add gpid if ortb2Imp.ext.gpid is not present', function () { + const updatedBidRequests = bidRequests.map(bidRequest => ({ + ...bidRequest, + ortb2Imp: { + ext: { + } + } + })); + + const request = spec.buildRequests(updatedBidRequests, bidderResquestDefault); + const payload = JSON.parse(request.data); + + return payload.data.forEach(bid => { + expect(bid).not.to.have.property('gpid'); + }); + }); }); function checkMediaTypesSizes(mediaTypes, expectedSizes) { diff --git a/test/spec/modules/telariaBidAdapter_spec.js b/test/spec/modules/telariaBidAdapter_spec.js index 25649115cc1..457dd568764 100644 --- a/test/spec/modules/telariaBidAdapter_spec.js +++ b/test/spec/modules/telariaBidAdapter_spec.js @@ -234,9 +234,7 @@ describe('TelariaAdapter', () => { }]; it('should get correct bid response', () => { - let expectedResponseKeys = ['bidderCode', 'width', 'height', 'statusMessage', 'adId', 'mediaType', 'source', - 'getStatusCode', 'getSize', 'requestId', 'cpm', 'creativeId', 'vastXml', - 'vastUrl', 'currency', 'netRevenue', 'ttl', 'ad', 'meta']; + let expectedResponseKeys = ['requestId', 'cpm', 'creativeId', 'vastXml', 'vastUrl', 'mediaType', 'width', 'height', 'currency', 'netRevenue', 'ttl', 'ad', 'meta']; let bidRequest = spec.buildRequests(stub, BIDDER_REQUEST)[0]; bidRequest.bidId = '1234'; diff --git a/test/spec/modules/timBidAdapter_spec.js b/test/spec/modules/timBidAdapter_spec.js deleted file mode 100644 index bf2d2e28510..00000000000 --- a/test/spec/modules/timBidAdapter_spec.js +++ /dev/null @@ -1,152 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/timBidAdapter.js'; - -describe('timAdapterTests', function () { - describe('bidRequestValidity', function () { - it('bidRequest with publisherid and placementCode params', function () { - expect(spec.isBidRequestValid({ - bidder: 'tim', - params: { - publisherid: 'testid', - placementCode: 'testplacement' - } - })).to.equal(true); - }); - - it('bidRequest with only publisherid', function () { - expect(spec.isBidRequestValid({ - bidder: 'tim', - params: { - publisherid: 'testid' - } - })).to.equal(false); - }); - - it('bidRequest with only placementCode', function () { - expect(spec.isBidRequestValid({ - bidder: 'tim', - params: { - placementCode: 'testplacement' - } - })).to.equal(false); - }); - - it('bidRequest without params', function () { - expect(spec.isBidRequestValid({ - bidder: 'tim', - })).to.equal(false); - }); - }); - - describe('buildRequests', function () { - const validBidRequests = [{ - 'bidder': 'tim', - 'params': {'placementCode': 'placementCode', 'publisherid': 'testpublisherid'}, - 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, - 'adUnitCode': 'adUnitCode', - 'transactionId': 'transactionId', - 'sizes': [[300, 250]], - 'bidId': 'bidId', - 'bidderRequestId': 'bidderRequestId', - 'auctionId': 'auctionId', - 'src': 'client', - 'bidRequestsCount': 1 - }]; - - it('bidRequest method', function () { - const requests = spec.buildRequests(validBidRequests); - expect(requests[0].method).to.equal('GET'); - }); - - it('bidRequest url', function () { - const requests = spec.buildRequests(validBidRequests); - expect(requests[0].url).to.exist; - }); - - it('bidRequest data', function () { - const requests = spec.buildRequests(validBidRequests); - expect(requests[0].data).to.exist; - }); - - it('bidRequest options', function () { - const requests = spec.buildRequests(validBidRequests); - expect(requests[0].options).to.exist; - }); - }); - - describe('interpretResponse', function () { - const bidRequest = { - 'method': 'GET', - 'url': 'https://bidder.url/api/prebid/testpublisherid/header-bid-tag-0?br=%7B%22id%22%3A%223a3ac0d7fc2548%22%2C%22imp%22%3A%5B%7B%22id%22%3A%22251b8a6d3aac3e%22%2C%22banner%22%3A%7B%22w%22%3A300%2C%22h%22%3A250%7D%2C%22tagid%22%3A%22header-bid-tag-0%22%7D%5D%2C%22site%22%3A%7B%22domain%22%3A%22www.chinatimes.com%22%2C%22page%22%3A%22https%3A%2F%2Fwww.chinatimes.com%2Fa%22%2C%22publisher%22%3A%7B%22id%22%3A%22testpublisherid%22%7D%7D%2C%22device%22%3A%7B%22language%22%3A%22en%22%2C%22w%22%3A300%2C%22h%22%3A250%2C%22js%22%3A1%2C%22ua%22%3A%22Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F71.0.3578.98%20Safari%2F537.36%22%7D%2C%22bidId%22%3A%22251b8a6d3aac3e%22%7D', - 'data': '', - 'options': {'withCredentials': false} - }; - - const serverResponse = { - 'body': { - 'id': 'id', - 'seatbid': [] - }, - 'headers': {} - }; - - it('check empty array response', function () { - const result = spec.interpretResponse(serverResponse, bidRequest); - expect(result).to.deep.equal([]); - }); - - const validBidRequest = { - 'method': 'GET', - 'url': 'https://bidder.url/api/v2/services/prebid/testpublisherid/placementCodeTest?br=%7B%22id%22%3A%2248640869bd9db94%22%2C%22imp%22%3A%5B%7B%22id%22%3A%224746fcaa11197f3%22%2C%22banner%22%3A%7B%22w%22%3A300%2C%22h%22%3A250%7D%2C%22tagid%22%3A%22placementCodeTest%22%7D%5D%2C%22site%22%3A%7B%22domain%22%3A%22mediamart.tv%22%2C%22page%22%3A%22https%3A%2F%2Fmediamart.tv%2Fsas%2Ftests%2FDesktop%2Fcaesar%2Fdfptest.html%22%2C%22publisher%22%3A%7B%22id%22%3A%22testpublisherid%22%7D%7D%2C%22device%22%3A%7B%22language%22%3A%22en%22%2C%22w%22%3A300%2C%22h%22%3A250%2C%22js%22%3A1%2C%22ua%22%3A%22Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F71.0.3578.98%20Safari%2F537.36%22%7D%2C%22bidId%22%3A%224746fcaa11197f3%22%7D', - 'data': '', - 'options': {'withCredentials': false} - }; - const validServerResponse = { - 'body': {'id': 'id', - 'seatbid': [ - {'bid': [{'id': 'id', - 'impid': 'impid', - 'price': 3, - 'nurl': 'https://bidder.url/api/v1/?price=${AUCTION_PRICE}&bidcur=USD&bidid=bidid=true', - 'adm': '', - 'adomain': [''], - 'cid': '1', - 'crid': '700', - 'w': 300, - 'h': 250 - }]}], - 'bidid': 'bidid', - 'cur': 'USD' - }, - 'headers': {} - }; - it('required keys', function () { - const result = spec.interpretResponse(validServerResponse, validBidRequest); - - let requiredKeys = [ - 'requestId', - 'creativeId', - 'adId', - 'cpm', - 'width', - 'height', - 'currency', - 'netRevenue', - 'ttl', - 'ad' - ]; - - let resultKeys = Object.keys(result[0]); - requiredKeys.forEach(function(key) { - expect(resultKeys.indexOf(key) !== -1).to.equal(true); - }); - }) - }); - - describe('getUserSyncs', function () { - it('check empty response getUserSyncs', function () { - const result = spec.getUserSyncs('', ''); - expect(result).to.deep.equal([]); - }); - }); -}); diff --git a/test/spec/modules/timeoutRtdProvider_spec.js b/test/spec/modules/timeoutRtdProvider_spec.js new file mode 100644 index 00000000000..88415a99b5e --- /dev/null +++ b/test/spec/modules/timeoutRtdProvider_spec.js @@ -0,0 +1,339 @@ + +import { timeoutRtdFunctions, timeoutSubmodule } from '../../../modules/timeoutRtdProvider' +import { expect } from 'chai'; +import * as ajax from 'src/ajax.js'; +import * as prebidGlobal from 'src/prebidGlobal.js'; + +const DEFAULT_USER_AGENT = window.navigator.userAgent; +const DEFAULT_CONNECTION = window.navigator.connection; + +const PC_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246'; +const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'; +const TABLET_USER_AGENT = 'Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148'; + +function resetUserAgent() { + window.navigator.__defineGetter__('userAgent', () => DEFAULT_USER_AGENT); +}; + +function setUserAgent(userAgent) { + window.navigator.__defineGetter__('userAgent', () => userAgent); +} + +function resetConnection() { + window.navigator.__defineGetter__('connection', () => DEFAULT_CONNECTION); +} +function setConnectionType(connectionType) { + window.navigator.__defineGetter__('connection', () => { return {'type': connectionType} }); +} + +describe('getDeviceType', () => { + afterEach(() => { + resetUserAgent(); + }); + + [ + // deviceType, userAgent, deviceTypeNum + ['pc', PC_USER_AGENT, 2], + ['mobile', MOBILE_USER_AGENT, 4], + ['tablet', TABLET_USER_AGENT, 5], + ].forEach(function(args) { + const [deviceType, userAgent, deviceTypeNum] = args; + it(`should be able to recognize ${deviceType} devices`, () => { + setUserAgent(userAgent); + const res = timeoutRtdFunctions.getDeviceType(); + expect(res).to.equal(deviceTypeNum) + }) + }) +}); + +describe('getConnectionSpeed', () => { + afterEach(() => { + resetConnection(); + }); + [ + // connectionType, connectionSpeed + ['slow-2g', 'slow'], + ['2g', 'slow'], + ['3g', 'medium'], + ['bluetooth', 'fast'], + ['cellular', 'fast'], + ['ethernet', 'fast'], + ['wifi', 'fast'], + ['wimax', 'fast'], + ['4g', 'fast'], + ['not known', 'unknown'], + [undefined, 'unknown'], + ].forEach(function(args) { + const [connectionType, connectionSpeed] = args; + it(`should be able to categorize connection speed when the connection type is ${connectionType}`, () => { + setConnectionType(connectionType); + const res = timeoutRtdFunctions.getConnectionSpeed(); + expect(res).to.equal(connectionSpeed) + }) + }) +}); + +describe('Timeout modifier calculations', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should be able to detect video ad units', () => { + let adUnits = [] + let res = timeoutRtdFunctions.checkVideo(adUnits); + expect(res).to.be.false; + + adUnits = [{ + mediaTypes: { + video: [] + } + }]; + res = timeoutRtdFunctions.checkVideo(adUnits); + expect(res).to.be.true; + + adUnits = [{ + mediaTypes: { + banner: [] + } + }]; + res = timeoutRtdFunctions.checkVideo(adUnits); + expect(res).to.be.false; + }); + + it('should calculate the timeout modifier for video', () => { + sandbox.stub(timeoutRtdFunctions, 'checkVideo').returns(true); + const rules = { + includesVideo: { + 'true': 200, + 'false': 50 + } + } + const res = timeoutRtdFunctions.calculateTimeoutModifier([], rules); + expect(res).to.equal(200) + }); + + it('should calculate the timeout modifier for connectionSpeed', () => { + sandbox.stub(timeoutRtdFunctions, 'getConnectionSpeed').returns('slow'); + const rules = { + connectionSpeed: { + 'slow': 200, + 'medium': 100, + 'fast': 50 + } + } + const res = timeoutRtdFunctions.calculateTimeoutModifier([], rules); + expect(res).to.equal(200); + }); + + it('should calculate the timeout modifier for deviceType', () => { + sandbox.stub(timeoutRtdFunctions, 'getDeviceType').returns(4); + const rules = { + deviceType: { + '2': 50, + '4': 100, + '5': 200 + }, + } + const res = timeoutRtdFunctions.calculateTimeoutModifier([], rules); + expect(res).to.equal(100) + }); + + it('should calculate the timeout modifier for ranged numAdunits', () => { + const rules = { + numAdUnits: { + '1-5': 100, + '6-10': 200, + '11-15': 300, + } + } + const adUnits = [1, 2, 3, 4, 5, 6]; + const res = timeoutRtdFunctions.calculateTimeoutModifier(adUnits, rules); + expect(res).to.equal(200) + }); + + it('should calculate the timeout modifier for exact numAdunits', () => { + const rules = { + numAdUnits: { + '1': 100, + '2': 200, + '3': 300, + '4-5': 400, + } + } + const adUnits = [1, 2]; + const res = timeoutRtdFunctions.calculateTimeoutModifier(adUnits, rules); + expect(res).to.equal(200); + }); + + it('should add up all the modifiers when all the rules are present', () => { + sandbox.stub(timeoutRtdFunctions, 'getConnectionSpeed').returns('slow'); + sandbox.stub(timeoutRtdFunctions, 'getDeviceType').returns(4); + const rules = { + connectionSpeed: { + 'slow': 200, + 'medium': 100, + 'fast': 50 + }, + deviceType: { + '2': 50, + '4': 100, + '5': 200 + }, + includesVideo: { + 'true': 200, + 'false': 50 + }, + numAdUnits: { + '1': 100, + '2': 200, + '3': 300, + '4-5': 400, + } + } + const res = timeoutRtdFunctions.calculateTimeoutModifier([{ + mediaTypes: { + video: [] + } + }], rules); + expect(res).to.equal(600); + }); +}); + +describe('Timeout RTD submodule', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should init successfully', () => { + expect(timeoutSubmodule.init()).to.equal(true); + }); + + it('should make a request to the endpoint url if it is provided, and handle the response', () => { + const response = '{"deviceType":{ "2": 50, "4": 100, "5": 200 }}' + const ajaxStub = sandbox.stub().callsFake(function (url, callbackObj) { + callbackObj.success(response); + }); + sandbox.stub(ajax, 'ajaxBuilder').callsFake(function () { return ajaxStub }); + + const reqBidsConfigObj = {} + const expectedLink = 'https://somelink.json' + const config = { + 'name': 'timeout', + 'params': { + 'endpoint': { + url: expectedLink + } + } + } + const handleTimeoutIncrementStub = sandbox.stub(timeoutRtdFunctions, 'handleTimeoutIncrement'); + timeoutSubmodule.getBidRequestData(reqBidsConfigObj, function() {}, config) + + expect(ajaxStub.calledWith(expectedLink)).to.be.true; + expect(handleTimeoutIncrementStub.calledWith(reqBidsConfigObj, JSON.parse(response))).to.be.true; + }); + + it('should make a request to the endpoint url and ignore the rules object if the endpoint is provided', () => { + const ajaxStub = sandbox.stub().callsFake((url, callbackObj) => {}); + sandbox.stub(ajax, 'ajaxBuilder').callsFake(() => ajaxStub); + const expectedLink = 'https://somelink.json' + const config = { + 'name': 'timeout', + 'params': { + 'endpoint': { + url: expectedLink + }, + 'rules': { + 'includesVideo': { + 'true': 200, + }, + } + } + } + timeoutSubmodule.getBidRequestData({}, function() {}, config); + expect(ajaxStub.calledWith(expectedLink)).to.be.true; + }); + + it('should use the rules object if there is no endpoint url', () => { + const config = { + 'name': 'timeout', + 'params': { + 'rules': { + 'includesVideo': { + 'true': 200, + }, + } + } + } + const handleTimeoutIncrementStub = sandbox.stub(timeoutRtdFunctions, 'handleTimeoutIncrement'); + const reqBidsConfigObj = {}; + timeoutSubmodule.getBidRequestData(reqBidsConfigObj, function() {}, config); + expect(handleTimeoutIncrementStub.calledWith(reqBidsConfigObj, config.params.rules)).to.be.true; + }); + + it('should exit quietly if no relevant timeout config is found', () => { + const callback = sandbox.stub() + const ajaxStub = sandbox.stub().callsFake((url, callbackObj) => {}); + sandbox.stub(ajax, 'ajaxBuilder').callsFake(function() { return ajaxStub }); + const handleTimeoutIncrementStub = sandbox.stub(timeoutRtdFunctions, 'handleTimeoutIncrement'); + + timeoutSubmodule.getBidRequestData({}, callback, {}); + + expect(handleTimeoutIncrementStub.called).to.be.false; + expect(callback.called).to.be.true; + expect(ajaxStub.called).to.be.false; + }); + + it('should be able to increment the timeout with the calculated timeout modifier', () => { + const baseTimeout = 100; + const getConfigStub = sandbox.stub().returns(baseTimeout); + sandbox.stub(prebidGlobal, 'getGlobal').callsFake(() => { + return { + getConfig: getConfigStub + } + }); + + const reqBidsConfigObj = {adUnits: [1, 2, 3]} + const addedTimeout = 400; + const rules = { + numAdUnits: { + '3-5': addedTimeout, + } + } + + timeoutRtdFunctions.handleTimeoutIncrement(reqBidsConfigObj, rules) + expect(reqBidsConfigObj.timeout).to.be.equal(baseTimeout + addedTimeout); + }); + + it('should be able to increment the timeout with the calculated timeout modifier when there are multiple matching rules', () => { + const baseTimeout = 100; + const getConfigStub = sandbox.stub().returns(baseTimeout); + sandbox.stub(prebidGlobal, 'getGlobal').callsFake(() => { + return { + getConfig: getConfigStub + } + }); + + const reqBidsConfigObj = {adUnits: [1, 2, 3]} + const addedTimeout = 400; + const rules = { + numAdUnits: { + '3-5': addedTimeout / 2, + }, + includesVideo: { + 'false': addedTimeout / 2, + } + } + timeoutRtdFunctions.handleTimeoutIncrement(reqBidsConfigObj, rules) + expect(reqBidsConfigObj.timeout).to.be.equal(baseTimeout + addedTimeout); + }); +}); diff --git a/test/spec/modules/topRTBBidAdapter_spec.js b/test/spec/modules/topRTBBidAdapter_spec.js deleted file mode 100644 index 9b97917a0b6..00000000000 --- a/test/spec/modules/topRTBBidAdapter_spec.js +++ /dev/null @@ -1,67 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/topRTBBidAdapter.js'; - -describe('topRTBBidAdapterTests', function () { - it('validate_pub_params', function () { - expect(spec.isBidRequestValid({ - bidder: 'topRTB', - params: { - adUnitId: 'c5c06f77430c4c33814a0577cb4cc978' - }, - adName: 'banner' - })); - }); - - it('validate_generated_params', function () { - let bidRequestData = [{ - bidId: 'bid12345', - bidder: 'topRTB', - adName: 'banner', - adType: '{"banner":{"sizes":[[]]}}', - params: { - adUnitId: 'c5c06f77430c4c33814a0577cb4cc978' - }, - sizes: [[728, 90]] - }]; - - let request = spec.buildRequests(bidRequestData); - const current_url = request.url; - const search_params = current_url.searchParams; - }); - - it('validate_response_params', function () { - let bidRequestData = { - data: { - bidId: 'bid12345' - } - }; - - let serverResponse = { - body: [{ - 'cpm': 1, - 'mediadata': "Banner 728x90", - 'width': 728, - 'currency': 'USD', - 'id': 'cd95dffec6b645afbc4e5aa9f68f2ff3', - 'type': 'RICHMEDIA', - 'ttl': 4000, - 'bidId': 'bid12345', - 'status': 'success', - 'height': 90}], - 'adName': 'banner', - 'vastXml': '', - 'mediaType': 'banner', - 'tracking': 'https://ssp.toprtb.com/ssp/tracking?F0cloTiKIw%2BjZ2UNDvlKGn5%2FWoAO9cnlAUDm6gFBM8bImY2fKo%2BMTvI0XvXzFTZSb5v8o4EUbPId9hckptTqA4QPaWvpVYCRKRZceXNa4kjtvfm4j2e%2FcRKgkns2goHXi7IZC0sBIbE77WWg%2BPBYv%2BCu84H%2FSH69mi%2FDaWcQlfaEOdkaJdstJEkaZtkgWnFnS7aagte%2BfdEbOqcTxq5hzj%2BZ4NZbwgReuWTQZbfrMWjkXFbn%2B35vZuI319o6XH9n9fKLS4xp8zstXfQT2oSgjw1NmrwqRKf1efB1UaWlS1TbkSqxZ7Kcy7nJvAZrDk0tzcSeIxe4VfHpwgPPs%2BueUeGwz3o7OCh7H1sCmogSrmJFB9JTeXudFjC13iANAtu4SvG9bGIbiJxS%2BNfkjy2mLFm8kSIcIobjNkMEcUAwmoqJNRndwb66a3Iovk2NTo0Ly%2FV7Y5ECPcS5%2FPBrIEOuQXS5SNUPRWKoklX5nexHtOc%3D', - 'impression': 'https://ssp.toprtb.com/ssp/impression?id=64f29f7b226249f19925a680a506b32d' - }; - - let bids = spec.interpretResponse(serverResponse, bidRequestData); - expect(bids).to.have.lengthOf(1); - let bid = bids[0]; - expect(bid.cpm).to.equal(1); - expect(bid.currency).to.equal('USD'); - expect(bid.width).to.equal(728); - expect(bid.height).to.equal(90); - expect(bid.requestId).to.equal('bid12345'); - }); -}); diff --git a/test/spec/modules/tribeosBidAdapter_spec.js b/test/spec/modules/tribeosBidAdapter_spec.js deleted file mode 100644 index fd7f7087eb7..00000000000 --- a/test/spec/modules/tribeosBidAdapter_spec.js +++ /dev/null @@ -1,86 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/tribeosBidAdapter.js'; - -describe('tribeosBidAdapter', function() { - describe('isBidRequestValid', function() { - it('should return true if all parameters are passed', function() { - expect(spec.isBidRequestValid({ - bidder: 'tribeos', - params: { - placementId: '12345' - } - })).to.equal(true); - }); - - it('should return false is placementId is missing', function() { - expect(spec.isBidRequestValid({ - bidder: 'tribeos', - params: {} - })).to.equal(false); - }); - }); - - it('validate bid request data from backend', function() { - let bidRequestData = [{ - bidId: 'bid12', - bidder: 'tribeos', - mediaTypes: { - banner: { - sizes: [ - [300, 250] - ], - } - }, - params: { - placementId: 'test-bid' - } - }]; - - let request = spec.buildRequests(bidRequestData); - let payload = JSON.parse(request[0].data); - - expect(payload.bidId).to.equal('bid12'); - }); - - it('validate response parameters', function() { - let bidRequestData = { - data: { - bidId: '21f3e9c3ce92f2' - } - }; - - let serverResponse = { - body: { - 'id': '5e23a6c74314aa782328376f5954', - 'bidid': '5e23a6c74314aa782328376f5954', - 'seatbid': [{ - 'bid': [{ - 'id': '5e23a6c74314aa782328376f5954', - 'impid': '21f3e9c3ce92f2', - 'price': 1.1, - 'adid': '5e23a6c74314aa782328376f5954', - 'adm': '', - 'cid': '5e1eea895d37673aef2134825195rnd2', - 'crid': '5e0b71e6823bb66fcb6c9858', - 'h': 250, - 'w': 300 - }], - 'seats': '1' - }], - 'cur': 'USD' - } - }; - - let bids = spec.interpretResponse(serverResponse, bidRequestData); - expect(bids).to.have.lengthOf(1); - let bid = bids[0]; - - expect(bid.cpm).to.equal(1.1); - expect(bid.currency).to.equal('USD'); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.netRevenue).to.equal(true); - expect(bid.requestId).to.equal('21f3e9c3ce92f2'); - expect(bid.ad).to.equal(''); - }); -}); diff --git a/test/spec/modules/trionBidAdapter_spec.js b/test/spec/modules/trionBidAdapter_spec.js index ae329b4a028..0fc03caa563 100644 --- a/test/spec/modules/trionBidAdapter_spec.js +++ b/test/spec/modules/trionBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import * as utils from 'src/utils.js'; import {spec, acceptPostMessage, getStorageData, setStorageData} from 'modules/trionBidAdapter.js'; +import {deepClone} from 'src/utils.js'; const CONSTANTS = require('src/constants.json'); const adloader = require('src/adloader'); @@ -316,6 +317,14 @@ describe('Trion adapter tests', function () { expect(response[0].cpm).to.equal(bidCpm); TRION_BID_RESPONSE.result.cpm = 100; }); + + it('advertiserDomains is included when sent by server', function () { + TRION_BID_RESPONSE.result.adomain = ['test_adomain']; + let response = spec.interpretResponse({body: TRION_BID_RESPONSE}, {bidRequest: TRION_BID}); + expect(Object.keys(response[0].meta)).to.include.members(['advertiserDomains']); + expect(response[0].meta.advertiserDomains).to.deep.equal(['test_adomain']); + delete TRION_BID_RESPONSE.result.adomain; + }); }); describe('getUserSyncs', function () { diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 30377ec0a5d..6f2674dadc5 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -568,6 +568,134 @@ describe('triplelift adapter', function () { expect(payload.user.ext.eids).to.have.lengthOf(4); }); + it('should remove malformed ids that would otherwise break call', function () { + let tdidId = '6bca7f6b-a98a-46c0-be05-6020f7604598'; + let idlEnvId = null; // fail; can't be null + let criteoId = '53e30ea700424f7bbdd793b02abc5d7'; + let pubcid = ''; // fail; can't be empty string + + let bidRequestsMultiple = [ + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } } + ]; + + let request = tripleliftAdapterSpec.buildRequests(bidRequestsMultiple, bidderRequest); + let payload = request.data; + + expect(payload.user).to.deep.equal({ + ext: { + eids: [ + { + source: 'adserver.org', + uids: [ + { + id: tdidId, + ext: { rtiPartner: 'TDID' } + } + ], + }, + { + source: 'criteo.com', + uids: [ + { + id: criteoId, + ext: { rtiPartner: 'criteoId' } + } + ] + } + ] + } + }); + + expect(payload.user.ext.eids).to.be.an('array'); + expect(payload.user.ext.eids).to.have.lengthOf(2); + + tdidId = {}; // fail; can't be empty object + idlEnvId = { id: '987654' }; // pass + criteoId = [{ id: '123456' }]; // fail; can't be an array + pubcid = '3261d8ad-435d-481d-abd1-9f1a9ec99f0e'; // pass + + bidRequestsMultiple = [ + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } } + ]; + + request = tripleliftAdapterSpec.buildRequests(bidRequestsMultiple, bidderRequest); + payload = request.data; + + expect(payload.user).to.deep.equal({ + ext: { + eids: [ + { + source: 'liveramp.com', + uids: [ + { + id: '987654', + ext: { rtiPartner: 'idl' } + } + ] + }, + { + source: 'pubcid.org', + uids: [ + { + id: pubcid, + ext: { rtiPartner: 'pubcid' } + } + ] + } + ] + } + }); + + expect(payload.user.ext.eids).to.be.an('array'); + expect(payload.user.ext.eids).to.have.lengthOf(2); + + tdidId = { id: '987654' }; // pass + idlEnvId = { id: 987654 }; // fail; can't be an int + criteoId = '53e30ea700424f7bbdd793b02abc5d7'; // pass + pubcid = { id: '' }; // fail; can't be an empty string + + bidRequestsMultiple = [ + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } } + ]; + + request = tripleliftAdapterSpec.buildRequests(bidRequestsMultiple, bidderRequest); + payload = request.data; + + expect(payload.user).to.deep.equal({ + ext: { + eids: [ + { + source: 'adserver.org', + uids: [ + { + id: '987654', + ext: { rtiPartner: 'TDID' } + } + ], + }, + { + source: 'criteo.com', + uids: [ + { + id: criteoId, + ext: { rtiPartner: 'criteoId' } + } + ] + } + ] + } + }); + + expect(payload.user.ext.eids).to.be.an('array'); + expect(payload.user.ext.eids).to.have.lengthOf(2); + }); + it('should return a query string for TL call', function () { const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const url = request.url; @@ -809,6 +937,8 @@ describe('triplelift adapter', function () { expect(result).to.have.length(2); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); expect(Object.keys(result[1])).to.have.members(Object.keys(expectedResponse[1])); + expect(result[0].ttl).to.equal(300); + expect(result[1].ttl).to.equal(3600); }); it('should return multiple responses to support SRA', function () { diff --git a/test/spec/modules/truereachBidAdapter_spec.js b/test/spec/modules/truereachBidAdapter_spec.js index 36441722648..3c78c4b848d 100644 --- a/test/spec/modules/truereachBidAdapter_spec.js +++ b/test/spec/modules/truereachBidAdapter_spec.js @@ -40,7 +40,7 @@ describe('truereachBidAdapterTests', function () { expect(req_data.imp[0].id).to.equal('34ce3f3b15190a'); expect(req_data.imp[0].banner.w).to.equal(300); expect(req_data.imp[0].banner.h).to.equal(250); - expect(req_data.imp[0].bidfloor).to.equal(0.1); + expect(req_data.imp[0].bidfloor).to.equal(0); }); it('validate_response_params', function () { diff --git a/test/spec/modules/trustpidSystem_spec.js b/test/spec/modules/trustpidSystem_spec.js new file mode 100644 index 00000000000..97e87e3a303 --- /dev/null +++ b/test/spec/modules/trustpidSystem_spec.js @@ -0,0 +1,232 @@ +import { expect } from 'chai'; +import { trustpidSubmodule } from 'modules/trustpidSystem.js'; +import { storage } from 'modules/trustpidSystem.js'; + +describe('trustpid System', () => { + const connectDataKey = 'fcIdConnectData'; + const connectDomainKey = 'fcIdConnectDomain'; + + const getStorageData = (idGraph) => { + if (!idGraph) { + idGraph = {id: 501, domain: ''}; + } + return { + 'connectId': { + 'idGraph': [idGraph], + } + } + }; + + it('should have the correct module name declared', () => { + expect(trustpidSubmodule.name).to.equal('trustpid'); + }); + + describe('trustpid getId()', () => { + afterEach(() => { + storage.removeDataFromLocalStorage(connectDataKey); + storage.removeDataFromLocalStorage(connectDomainKey); + }); + + it('it should return object with key callback', () => { + expect(trustpidSubmodule.getId()).to.have.property('callback'); + }); + + it('should return object with key callback with value type - function', () => { + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData())); + expect(trustpidSubmodule.getId()).to.have.property('callback'); + expect(typeof trustpidSubmodule.getId().callback).to.be.equal('function'); + }); + + it('tests if localstorage & JSON works properly ', () => { + const idGraph = { + 'domain': 'domainValue', + 'umid': 'umidValue', + }; + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + expect(JSON.parse(storage.getDataFromLocalStorage(connectDataKey))).to.have.property('connectId'); + }); + + it('returns {callback: func} if domains don\'t match', () => { + const idGraph = { + 'domain': 'domainValue', + 'umid': 'umidValue', + }; + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('differentDomainValue')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + expect(trustpidSubmodule.getId()).to.have.property('callback'); + }); + + it('returns {id: {trustpid: data.trustpid}} if we have the right data stored in the localstorage ', () => { + const idGraph = { + 'domain': 'uat.mno.link', + 'umid': 'umidValue', + }; + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('uat.mno.link')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + const response = trustpidSubmodule.getId(); + expect(response).to.have.property('id'); + expect(response.id).to.have.property('trustpid'); + expect(response.id.trustpid).to.be.equal('umidValue-xxxx'); + }); + + it('returns {trustpid: data.trustpid} if we have the right data stored in the localstorage right after the callback is called', (done) => { + const idGraph = { + 'domain': 'uat.mno.link', + 'umid': 'umidValue', + }; + const response = trustpidSubmodule.getId(); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('uat.mno.link')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + response.callback(function (result) { + expect(result).to.not.be.null; + expect(result).to.have.property('trustpid'); + expect(result.trustpid).to.be.equal('umidValue-xxxx'); + done() + }) + } + }); + + it('returns null if domains don\'t match', (done) => { + const idGraph = { + 'domain': 'uat.mno.link', + 'umid': 'umidValue', + }; + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('differentDomainValue')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + + const response = trustpidSubmodule.getId(); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + setTimeout(() => { + expect(JSON.parse(storage.getDataFromLocalStorage(connectDomainKey))).to.be.equal('differentDomainValue'); + }, 100) + response.callback(function (result) { + expect(result).to.be.null; + done() + }) + } + }); + + it('returns {trustpid: data.trustpid} if we have the right data stored in the localstorage right after 500ms delay', (done) => { + const idGraph = { + 'domain': 'uat.mno.link', + 'umid': 'umidValue', + }; + + const response = trustpidSubmodule.getId(); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + setTimeout(() => { + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('uat.mno.link')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + }, 500); + response.callback(function (result) { + expect(result).to.not.be.null; + expect(result).to.have.property('trustpid'); + expect(result.trustpid).to.be.equal('umidValue-xxxx'); + done() + }) + } + }); + + it('returns null if we have the data stored in the localstorage after 500ms delay and the max (waiting) delay is only 200ms ', (done) => { + const idGraph = { + 'domain': 'uat.mno.link', + 'umid': 'umidValue', + }; + + const response = trustpidSubmodule.getId({params: {maxDelayTime: 200}}); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + setTimeout(() => { + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('uat.mno.link')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + }, 500); + response.callback(function (result) { + expect(result).to.be.null; + done() + }) + } + }); + }); + + describe('trustpid decode()', () => { + const VALID_API_RESPONSES = [ + { + expected: '32a97f612', + payload: { + trustpid: '32a97f612' + } + }, + { + expected: '32a97f61', + payload: { + trustpid: '32a97f61', + } + }, + ]; + VALID_API_RESPONSES.forEach(responseData => { + it('should return a newly constructed object with the trustpid for a payload with {trustpid: value}', () => { + expect(trustpidSubmodule.decode(responseData.payload)).to.deep.equal( + {trustpid: responseData.expected} + ); + }); + }); + + [{}, '', {foo: 'bar'}].forEach((response) => { + it(`should return null for an invalid response "${JSON.stringify(response)}"`, () => { + expect(trustpidSubmodule.decode(response)).to.be.null; + }); + }); + }); + + describe('trustpid messageHandler for acronyms', () => { + afterEach(() => { + storage.removeDataFromLocalStorage(connectDataKey); + storage.removeDataFromLocalStorage(connectDomainKey); + }); + + const domains = [ + {domain: 'tmi.mno.link', acronym: 'ndye'}, + {domain: 'tmi.vodafone.de', acronym: 'pqnx'}, + {domain: 'tmi.telekom.de', acronym: 'avgw'}, + {domain: 'tmi.tmid.es', acronym: 'kjws'}, + {domain: 'uat.mno.link', acronym: 'xxxx'}, + {domain: 'es.tmiservice.orange.com', acronym: 'aplw'}, + ]; + + domains.forEach(({domain, acronym}) => { + it(`correctly sets trustpid value and acronym to ${acronym} for ${domain} domain`, (done) => { + const idGraph = { + 'domain': domain, + 'umid': 'umidValue', + }; + + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify(domain)); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + + const eventData = { + data: `{\"msgType\":\"MNOSELECTOR\",\"body\":{\"url\":\"https://${domain}/some/path\"}}` + }; + + window.dispatchEvent(new MessageEvent('message', eventData)); + + const response = trustpidSubmodule.getId(); + expect(response).to.have.property('id'); + expect(response.id).to.have.property('trustpid'); + expect(response.id.trustpid).to.be.equal(`umidValue-${acronym}`); + done(); + }); + }); + }); +}); diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index c0155f87ab6..2a0832f11e8 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -40,194 +40,6 @@ describe('TrustXAdapter', function () { }); describe('buildRequests', function () { - function parseRequest(url) { - const res = {}; - url.split('&').forEach((it) => { - const couple = it.split('='); - res[couple[0]] = decodeURIComponent(couple[1]); - }); - return res; - } - - const bidderRequest = { - refererInfo: { - referer: 'https://example.com' - } - }; - const referrer = bidderRequest.refererInfo.referer; - - let bidRequests = [ - { - 'bidder': 'trustx', - 'params': { - 'uid': '43' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }, - { - 'bidder': 'trustx', - 'params': { - 'uid': '43' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90], [300, 250]], - 'bidId': '3150ccb55da321', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }, - { - 'bidder': 'trustx', - 'params': { - 'uid': '45' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '42dbe3a7168a6a', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - it('should attach valid params to the tag', function () { - const request = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '43'); - expect(payload).to.have.property('sizes', '300x250,300x600'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - expect(payload).to.have.property('wrapperType', 'Prebid_js'); - expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); - }); - - it('sizes must not be duplicated', function () { - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '43,43,45'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - }); - - it('pt parameter must be "gross" if params.priceType === "gross"', function () { - bidRequests[1].params.priceType = 'gross'; - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'gross'); - expect(payload).to.have.property('auids', '43,43,45'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - delete bidRequests[1].params.priceType; - }); - - it('pt parameter must be "net" or "gross"', function () { - bidRequests[1].params.priceType = 'some'; - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '43,43,45'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - delete bidRequests[1].params.priceType; - }); - - it('if gdprConsent is present payload must have gdpr params', function () { - const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR)[0]; - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '1'); - }); - - it('if gdprApplies is false gdpr_applies must be 0', function () { - const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: false}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR)[0]; - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '0'); - }); - - it('if gdprApplies is undefined gdpr_applies must be 1', function () { - const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA'}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR)[0]; - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '1'); - }); - - it('if usPrivacy is present payload must have us_privacy param', function () { - const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithUSP)[0]; - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('us_privacy', '1YNN'); - }); - - it('should convert keyword params to proper form and attaches to request', function () { - const bidRequestWithKeywords = [].concat(bidRequests); - bidRequestWithKeywords[1] = Object.assign({}, - bidRequests[1], - { - params: { - uid: '43', - keywords: { - single: 'val', - singleArr: ['val'], - singleArrNum: [5], - multiValMixed: ['value1', 2, 'value3'], - singleValNum: 123, - emptyStr: '', - emptyArr: [''], - badValue: {'foo': 'bar'} // should be dropped - } - } - } - ); - - const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest)[0]; - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload.keywords).to.be.an('string'); - payload.keywords = JSON.parse(payload.keywords); - - expect(payload.keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'singleArrNum', - 'value': ['5'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'singleValNum', - 'value': ['123'] - }, { - 'key': 'emptyStr' - }, { - 'key': 'emptyArr' - }]); - }); - }); - - describe('buildRequests with new format', function () { function parseRequest(data) { return JSON.parse(data); } @@ -245,7 +57,6 @@ describe('TrustXAdapter', function () { 'params': { 'uid': '43', 'bidFloor': 1.25, - 'useNewFormat': true }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], @@ -262,7 +73,6 @@ describe('TrustXAdapter', function () { 'bidder': 'trustx', 'params': { 'uid': '44', - 'useNewFormat': true }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], @@ -274,7 +84,6 @@ describe('TrustXAdapter', function () { 'bidder': 'trustx', 'params': { 'uid': '45', - 'useNewFormat': true }, 'adUnitCode': 'adunit-code-2', 'sizes': [[728, 90]], @@ -292,7 +101,6 @@ describe('TrustXAdapter', function () { 'bidder': 'trustx', 'params': { 'uid': '41', - 'useNewFormat': true }, 'adUnitCode': 'adunit-code-2', 'sizes': [[728, 90]], @@ -312,7 +120,7 @@ describe('TrustXAdapter', function () { ]; it('should attach valid params to the tag', function () { - const request = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; + const request = spec.buildRequests([bidRequests[0]], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.deep.equal({ @@ -340,7 +148,7 @@ describe('TrustXAdapter', function () { }); it('make possible to process request without mediaTypes', function () { - const request = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest)[0]; + const request = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.deep.equal({ @@ -377,7 +185,7 @@ describe('TrustXAdapter', function () { }); it('should attach valid params to the video tag', function () { - const request = spec.buildRequests(bidRequests.slice(0, 3), bidderRequest)[0]; + const request = spec.buildRequests(bidRequests.slice(0, 3), bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.deep.equal({ @@ -423,7 +231,7 @@ describe('TrustXAdapter', function () { }); it('should support mixed mediaTypes', function () { - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.deep.equal({ @@ -484,7 +292,7 @@ describe('TrustXAdapter', function () { it('if gdprConsent is present payload must have gdpr params', function () { const gdprBidderRequest = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); - const request = spec.buildRequests(bidRequests, gdprBidderRequest)[0]; + const request = spec.buildRequests(bidRequests, gdprBidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('user'); @@ -497,7 +305,7 @@ describe('TrustXAdapter', function () { it('if usPrivacy is present payload must have us_privacy param', function () { const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithUSP)[0]; + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('regs'); @@ -530,7 +338,7 @@ describe('TrustXAdapter', function () { userIdAsEids: eids }, bid); }); - const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest)[0]; + const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('user'); @@ -554,7 +362,7 @@ describe('TrustXAdapter', function () { schain: schain }, bid); }); - const request = spec.buildRequests(bidRequestsWithSChain, bidderRequest)[0]; + const request = spec.buildRequests(bidRequestsWithSChain, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('source'); @@ -578,7 +386,7 @@ describe('TrustXAdapter', function () { } }, bid); }); - const request = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest)[0]; + const request = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('user'); @@ -593,20 +401,110 @@ describe('TrustXAdapter', function () { expect(payload.site.content).to.deep.equal(jsContent); }); + it('if segment is present in permutive targeting, payload must have right params', function () { + const permSegments = [{id: 'test_perm_1'}, {id: 'test_perm_2'}]; + const bidRequestsWithPermutiveTargeting = bidRequests.map((bid) => { + return Object.assign({ + rtd: { + p_standard: { + targeting: { + segments: permSegments + } + } + } + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithPermutiveTargeting, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('user'); + expect(payload.user.data).to.deep.equal([{ + name: 'permutive', + segment: [ + {name: 'p_standard', value: permSegments[0].id}, + {name: 'p_standard', value: permSegments[1].id} + ] + }]); + }); + it('should contain the keyword values if it present in ortb2.(site/user)', function () { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'ortb2.user' ? {'keywords': 'foo'} : (arg === 'ortb2.site' ? {'keywords': 'bar'} : null)); - const request = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; + arg => arg === 'ortb2.user' ? {'keywords': 'foo,any'} : (arg === 'ortb2.site' ? {'keywords': 'bar'} : null)); + const keywords = { + 'site': { + 'somePublisher': [ + { + 'name': 'someName', + 'brandsafety': ['disaster'], + 'topic': ['stress', 'fear'] + } + ] + }, + 'user': { + 'formatedPublisher': [ + { + 'name': 'fomatedName', + 'segments': [ + { 'name': 'segName1', 'value': 'segVal1' }, + { 'name': 'segName2', 'value': 'segVal2' } + ] + } + ] + } + }; + const bidRequestWithKW = { ...bidRequests[0], params: { ...bidRequests[0].params, keywords } } + const request = spec.buildRequests([bidRequestWithKW], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); - expect(payload.ext.keywords).to.deep.equal([{'key': 'user', 'value': ['foo']}, {'key': 'context', 'value': ['bar']}]); + expect(payload.ext.keywords).to.deep.equal({ + 'site': { + 'somePublisher': [ + { + 'name': 'someName', + 'segments': [ + { 'name': 'brandsafety', 'value': 'disaster' }, + { 'name': 'topic', 'value': 'stress' }, + { 'name': 'topic', 'value': 'fear' } + ] + } + ], + 'ortb2': [ + { + 'name': 'keywords', + 'segments': [ + { 'name': 'keywords', 'value': 'bar' } + ] + } + ] + }, + 'user': { + 'formatedPublisher': [ + { + 'name': 'fomatedName', + 'segments': [ + { 'name': 'segName1', 'value': 'segVal1' }, + { 'name': 'segName2', 'value': 'segVal2' } + ] + } + ], + 'ortb2': [ + { + 'name': 'keywords', + 'segments': [ + { 'name': 'keywords', 'value': 'foo' }, + { 'name': 'keywords', 'value': 'any' } + ] + } + ] + } + }); getConfigStub.restore(); }); it('shold be right tmax when timeout in config is less then timeout in bidderRequest', function() { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( arg => arg === 'bidderTimeout' ? 2000 : null); - const request = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; + const request = spec.buildRequests([bidRequests[0]], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload.tmax).to.equal(2000); @@ -615,12 +513,132 @@ describe('TrustXAdapter', function () { it('shold be right tmax when timeout in bidderRequest is less then timeout in config', function() { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( arg => arg === 'bidderTimeout' ? 5000 : null); - const request = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; + const request = spec.buildRequests([bidRequests[0]], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload.tmax).to.equal(3000); getConfigStub.restore(); }); + it('should contain imp[].ext.data.adserver if available', function() { + const ortb2Imp = [{ + ext: { + data: { + adserver: { + name: 'ad_server_name', + adslot: '/111111/slot' + }, + pbadslot: '/111111/slot' + } + } + }, { + ext: { + data: { + adserver: { + name: 'ad_server_name', + adslot: '/222222/slot' + }, + pbadslot: '/222222/slot' + } + } + }]; + const bidRequestsWithOrtb2Imp = bidRequests.slice(0, 3).map((bid, ind) => { + return Object.assign(ortb2Imp[ind] ? { ortb2Imp: ortb2Imp[ind] } : {}, bid); + }); + const request = spec.buildRequests(bidRequestsWithOrtb2Imp, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].ext).to.deep.equal({ + divid: bidRequests[0].adUnitCode, + data: ortb2Imp[0].ext.data, + gpid: ortb2Imp[0].ext.data.adserver.adslot + }); + expect(payload.imp[1].ext).to.deep.equal({ + divid: bidRequests[1].adUnitCode, + data: ortb2Imp[1].ext.data, + gpid: ortb2Imp[1].ext.data.adserver.adslot + }); + expect(payload.imp[2].ext).to.deep.equal({ + divid: bidRequests[2].adUnitCode + }); + }); + it('should contain imp[].instl if available', function() { + const ortb2Imp = [{ + instl: 1 + }, { + instl: 2, + ext: { + data: { + adserver: { + name: 'ad_server_name', + adslot: '/222222/slot' + }, + pbadslot: '/222222/slot' + } + } + }]; + const bidRequestsWithOrtb2Imp = bidRequests.slice(0, 3).map((bid, ind) => { + return Object.assign(ortb2Imp[ind] ? { ortb2Imp: ortb2Imp[ind] } : {}, bid); + }); + const request = spec.buildRequests(bidRequestsWithOrtb2Imp, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].instl).to.equal(1); + expect(payload.imp[1].ext).to.deep.equal({ + divid: bidRequests[1].adUnitCode, + data: ortb2Imp[1].ext.data, + gpid: ortb2Imp[1].ext.data.adserver.adslot + }); + expect(payload.imp[1].instl).to.equal(2); + expect(payload.imp[2].ext).to.deep.equal({ + divid: bidRequests[2].adUnitCode + }); + expect(payload.imp[2].instl).to.be.undefined; + }); + it('all id like request fields must be a string', function () { + const bidderRequestWithNumId = Object.assign({}, bidderRequest, { bidderRequestId: 123123, auctionId: 345345543 }); + + let bidRequestWithNumId = { + 'bidder': 'trustx', + 'params': { + 'uid': 43, + }, + 'adUnitCode': 111111, + 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': 23423423, + 'bidderRequestId': 123123, + 'auctionId': 345345543, + }; + + const request = spec.buildRequests([bidRequestWithNumId], bidderRequestWithNumId); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': '123123', + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': '345345543', + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': '23423423', + 'tagid': '43', + 'ext': {'divid': '111111'}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); + }); describe('floorModule', function () { const floorTestData = { @@ -633,7 +651,7 @@ describe('TrustXAdapter', function () { } }, bidRequests[1]); it('should return the value from getFloor if present', function () { - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const request = spec.buildRequests([bidRequest], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload.imp[0].bidfloor).to.equal(floorTestData.floor); @@ -642,7 +660,7 @@ describe('TrustXAdapter', function () { const bidfloor = 0.80; const bidRequestsWithFloor = { ...bidRequest }; bidRequestsWithFloor.params = Object.assign({bidFloor: bidfloor}, bidRequestsWithFloor.params); - const request = spec.buildRequests([bidRequestsWithFloor], bidderRequest)[0]; + const request = spec.buildRequests([bidRequestsWithFloor], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload.imp[0].bidfloor).to.equal(floorTestData.floor); @@ -651,7 +669,7 @@ describe('TrustXAdapter', function () { const bidfloor = 1.80; const bidRequestsWithFloor = { ...bidRequest }; bidRequestsWithFloor.params = Object.assign({bidFloor: bidfloor}, bidRequestsWithFloor.params); - const request = spec.buildRequests([bidRequestsWithFloor], bidderRequest)[0]; + const request = spec.buildRequests([bidRequestsWithFloor], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload.imp[0].bidfloor).to.equal(bidfloor); @@ -661,10 +679,10 @@ describe('TrustXAdapter', function () { describe('interpretResponse', function () { const responses = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 44, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 43, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'price': 0, 'auid': 45, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '659423fff799cb', 'price': 1.15, 'adm': '
test content 1
', 'auid': 43, 'h': 250, 'w': 300, 'adomain': ['somedomain.com']}], 'seat': '1'}, + {'bid': [{'impid': '4dff80cc4ee346', 'price': 0.5, 'adm': '
test content 2
', 'auid': 44, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '5703af74d0472a', 'price': 0.15, 'adm': '
test content 3
', 'auid': 43, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'impid': '659423faac49cb', 'price': 0, 'auid': 45, 'h': 250, 'w': 300}], 'seat': '1'}, {'bid': [{'price': 0, 'adm': '
test content 5
', 'h': 250, 'w': 300}], 'seat': '1'}, undefined, {'bid': [], 'seat': '1'}, @@ -685,7 +703,7 @@ describe('TrustXAdapter', function () { 'auctionId': '1cbd2feafe5e8b', } ]; - const request = spec.buildRequests(bidRequests)[0]; + const request = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '659423fff799cb', @@ -697,8 +715,11 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': ['somedomain.com'] + }, } ]; @@ -715,7 +736,7 @@ describe('TrustXAdapter', function () { }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], - 'bidId': '300bfeb0d71a5b', + 'bidId': '659423fff799cb', 'bidderRequestId': '2c2bb1972df9a', 'auctionId': '1fa09aee5c8c99', }, @@ -742,10 +763,10 @@ describe('TrustXAdapter', function () { 'auctionId': '1fa09aee5c8c99', } ]; - const request = spec.buildRequests(bidRequests)[0]; + const request = spec.buildRequests(bidRequests); const expectedResponse = [ { - 'requestId': '300bfeb0d71a5b', + 'requestId': '659423fff799cb', 'cpm': 1.15, 'creativeId': 43, 'dealId': undefined, @@ -754,8 +775,11 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': ['somedomain.com'] + }, }, { 'requestId': '4dff80cc4ee346', @@ -767,8 +791,11 @@ describe('TrustXAdapter', function () { 'ad': '
test content 2
', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, }, { 'requestId': '5703af74d0472a', @@ -780,8 +807,11 @@ describe('TrustXAdapter', function () { 'ad': '
test content 3
', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, } ]; @@ -825,18 +855,18 @@ describe('TrustXAdapter', function () { 'auctionId': '1fa09aee5c84d34', } ]; - const request = spec.buildRequests(bidRequests)[0]; + const request = spec.buildRequests(bidRequests); const result = spec.interpretResponse({'body': {'seatbid': responses.slice(3)}}, request); expect(result.length).to.equal(0); }); it('complicated case', function () { const fullResponse = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 44, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 43, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 4
', 'auid': 43, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 5
', 'auid': 44, 'h': 600, 'w': 350}], 'seat': '1'}, + {'bid': [{'impid': '2164be6358b9', 'price': 1.15, 'adm': '
test content 1
', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '4e111f1b66e4', 'price': 0.5, 'adm': '
test content 2
', 'auid': 44, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '26d6f897b516', 'price': 0.15, 'adm': '
test content 3
', 'auid': 43, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'impid': '326bde7fbf69', 'price': 0.15, 'adm': '
test content 4
', 'auid': 43, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '1751cd90161', 'price': 0.5, 'adm': '
test content 5
', 'auid': 44, 'h': 600, 'w': 350}], 'seat': '1'}, ]; const bidRequests = [ { @@ -895,7 +925,7 @@ describe('TrustXAdapter', function () { 'auctionId': '32a1f276cb87cb8', } ]; - const request = spec.buildRequests(bidRequests)[0]; + const request = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '2164be6358b9', @@ -907,8 +937,11 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, }, { 'requestId': '4e111f1b66e4', @@ -920,8 +953,11 @@ describe('TrustXAdapter', function () { 'ad': '
test content 2
', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, }, { 'requestId': '26d6f897b516', @@ -933,8 +969,11 @@ describe('TrustXAdapter', function () { 'ad': '
test content 3
', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, }, { 'requestId': '326bde7fbf69', @@ -946,8 +985,27 @@ describe('TrustXAdapter', function () { 'ad': '
test content 4
', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, + 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, + }, + { + 'requestId': '1751cd90161', + 'cpm': 0.5, + 'creativeId': 44, + 'dealId': undefined, + 'width': 350, + 'height': 600, + 'ad': '
test content 5
', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, } ]; @@ -957,8 +1015,8 @@ describe('TrustXAdapter', function () { it('dublicate uids and sizes in one slot', function () { const fullResponse = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '5126e301f4be', 'price': 1.15, 'adm': '
test content 1
', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '57b2ebe70e16', 'price': 0.5, 'adm': '
test content 2
', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, ]; const bidRequests = [ { @@ -995,7 +1053,7 @@ describe('TrustXAdapter', function () { 'auctionId': '35bcbc0f7e79c', } ]; - const request = spec.buildRequests(bidRequests)[0]; + const request = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '5126e301f4be', @@ -1007,8 +1065,11 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, }, { 'requestId': '57b2ebe70e16', @@ -1020,8 +1081,11 @@ describe('TrustXAdapter', function () { 'ad': '
test content 2
', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, } ]; @@ -1063,13 +1127,30 @@ describe('TrustXAdapter', function () { 'context': 'instream' } } + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '52' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '23312a43bc42', + 'bidderRequestId': '20394420a762a2', + 'auctionId': '140132d07b031', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } } ]; const response = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 50, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, - {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video'}], 'seat': '2'} + {'bid': [{'impid': '57dfefb80eca', 'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 50, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'impid': '5126e301f4be', 'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video'}], 'seat': '2'}, + {'bid': [{'impid': '23312a43bc42', 'price': 2.00, 'nurl': 'https://some_test_vast_url.com', 'auid': 52, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, ]; - const request = spec.buildRequests(bidRequests)[0]; + const request = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '57dfefb80eca', @@ -1080,13 +1161,32 @@ describe('TrustXAdapter', function () { 'height': 600, 'currency': 'USD', 'mediaType': 'video', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, 'vastXml': '\n<\/Ad>\n<\/VAST>', 'adResponse': { 'content': '\n<\/Ad>\n<\/VAST>' } - } + }, + { + 'requestId': '23312a43bc42', + 'cpm': 2.00, + 'creativeId': 52, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': false, + 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, + 'vastUrl': 'https://some_test_vast_url.com', + }, ]; const result = spec.interpretResponse({'body': {'seatbid': response}}, request); @@ -1144,11 +1244,11 @@ describe('TrustXAdapter', function () { } ]; const response = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 50, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, - {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video', w: 300, h: 250}], 'seat': '2'}, - {'bid': [{'price': 1.20, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 52, content_type: 'video', w: 300, h: 250}], 'seat': '2'} + {'bid': [{'impid': 'e6e65553fc8', 'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 50, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'impid': 'c8fdcb3f269f', 'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video', w: 300, h: 250}], 'seat': '2'}, + {'bid': [{'impid': '1de036c37685', 'price': 1.20, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 52, content_type: 'video', w: 300, h: 250}], 'seat': '2'} ]; - const request = spec.buildRequests(bidRequests)[0]; + const request = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': 'e6e65553fc8', @@ -1159,8 +1259,11 @@ describe('TrustXAdapter', function () { 'height': 600, 'currency': 'USD', 'mediaType': 'video', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, 'vastXml': '\n<\/Ad>\n<\/VAST>', 'adResponse': { 'content': '\n<\/Ad>\n<\/VAST>' @@ -1176,8 +1279,11 @@ describe('TrustXAdapter', function () { 'height': 250, 'currency': 'USD', 'mediaType': 'video', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, 'vastXml': '\n<\/Ad>\n<\/VAST>', 'adResponse': { 'content': '\n<\/Ad>\n<\/VAST>' @@ -1193,8 +1299,11 @@ describe('TrustXAdapter', function () { 'height': 250, 'currency': 'USD', 'mediaType': 'video', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, 'vastXml': '\n<\/Ad>\n<\/VAST>', 'adResponse': { 'content': '\n<\/Ad>\n<\/VAST>' diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js new file mode 100644 index 00000000000..099e5e56c33 --- /dev/null +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -0,0 +1,1313 @@ +import { expect } from 'chai'; +import { spec } from 'modules/ttdBidAdapter'; +import { deepClone } from 'src/utils.js'; +import { config } from 'src/config'; + +describe('ttdBidAdapter', function () { + function testBuildRequests(bidRequests, bidderRequestBase) { + let clonedBidderRequest = deepClone(bidderRequestBase); + clonedBidderRequest.bids = bidRequests; + return spec.buildRequests(bidRequests, clonedBidderRequest); + } + + describe('isBidRequestValid', function() { + function makeBid() { + return { + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '22222222', + 'placementId': 'some-PlacementId_1', + 'siteId': 'testSiteId' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + 'adUnitCode': 'adunit-code', + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + } + + describe('core', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(makeBid())).to.equal(true); + }); + + it('should return false when publisherId not passed', function () { + let bid = makeBid(); + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when supplySourceId not passed', function () { + let bid = makeBid(); + delete bid.params.supplySourceId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisherId is longer than 64 characters', function () { + let bid = makeBid(); + bid.params.publisherId = '1111111111111111111111111111111111111111111111111111111111111111111111'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when siteId not passed', function () { + let bid = makeBid(); + delete bid.params.siteId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when siteId is longer than 50 characters', function () { + let bid = makeBid(); + bid.params.siteId = '1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111' + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when placementId not passed', function () { + let bid = makeBid(); + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when the placementId is longer than 128 characters', function () { + let bid = makeBid(); + bid.params.placementId = '1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'; // 130 characters + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if neither mediaTypes.banner nor mediaTypes.video is passed', function () { + let bid = makeBid(); + delete bid.mediaTypes + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('banner', function () { + it('should return true if banner.pos is passed correctly', function () { + let bid = makeBid(); + bid.mediaTypes.banner.pos = 1; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('video', function () { + function makeBid() { + return { + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '22222222', + 'siteId': 'testSiteId123', + 'placementId': 'somePlacementId' + }, + 'mediaTypes': { + 'video': { + 'minduration': 5, + 'maxduration': 30, + 'playerSize': [640, 480], + 'api': [1, 3], + 'mimes': ['video/mp4'], + 'protocols': [2, 3, 5, 6] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + } + + it('should return true if required parameters are passed', function () { + let bid = makeBid(); + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if maxduration is missing', function () { + let bid = makeBid(); + delete bid.mediaTypes.video.maxduration; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if api is missing', function () { + let bid = makeBid(); + delete bid.mediaTypes.video.api; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if mimes is missing', function () { + let bid = makeBid(); + delete bid.mediaTypes.video.mimes; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if protocols is missing', function () { + let bid = makeBid(); + delete bid.mediaTypes.video.protocols; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + }); + + describe('getUserSyncs', function () { + it('to check the user sync iframe', function () { + const syncOptions = { + pixelEnabled: true + }; + const gdprConsentString = 'BON3G4EON3G4EAAABAENAA____ABl____A'; + const gdprConsent = { + consentString: gdprConsentString, + gdprApplies: true + }; + const uspConsent = '1YYY'; + + let syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + let params = new URLSearchParams(new URL(syncs[0].url).search); + expect(params.get('us_privacy')).to.equal(uspConsent); + expect(params.get('ust')).to.equal('image'); + expect(params.get('gdpr')).to.equal('1'); + expect(params.get('gdpr_consent')).to.equal(gdprConsentString); + }); + }); + + describe('buildRequests-banner', function () { + const baseBannerBidRequests = [{ + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '13144370', + 'placementId': '1gaa015', + 'siteId': 'testSiteId123' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'sizes': [[300, 250], [300, 600]], + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '8651474f-58b1-4368-b812-84f8c937a099', + 'bidId': '243310435309b5', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }]; + + const baseBidderRequest = { + 'bidderCode': 'ttd', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'bidderRequestId': '18084284054531', + 'auctionStart': 1540945362095, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'https://www.example.com/test', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://www.example.com/test' + ] + }, + 'start': 1540945362099, + 'doneCbCallCount': 0 + }; + + it('sends bid request to our endpoint that makes sense', function () { + const request = testBuildRequests(baseBannerBidRequests, baseBidderRequest); + expect(request.method).to.equal('POST'); + expect(request.url).to.be.not.empty; + expect(request.data).to.be.not.null; + }); + + it('sets impression id to ad unit\'s bid id', function () { + const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].id).to.equal('243310435309b5'); + }); + + it('sends bid requests to the correct endpoint', function () { + const url = testBuildRequests(baseBannerBidRequests, baseBidderRequest).url; + expect(url).to.equal('https://direct.adsrvr.org/bid/bidder/supplier'); + }); + + it('sends publisher id, site id, and placement id', function () { + const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; + expect(requestBody.site).to.be.not.null; + expect(requestBody.site.publisher).to.be.not.null; + expect(requestBody.imp[0].tagid).to.be.not.null; + expect(requestBody.site.publisher.id).to.equal(baseBannerBidRequests[0].params.publisherId); + expect(requestBody.site.id).to.equal(baseBannerBidRequests[0].params.siteId); + expect(requestBody.imp[0].tagid).to.equal(baseBannerBidRequests[0].params.placementId); + }); + + it('includes the ad size in the bid request', function () { + const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].banner.format[0].w).to.equal(300); + expect(requestBody.imp[0].banner.format[0].h).to.equal(250); + expect(requestBody.imp[0].banner.format[1].w).to.equal(300); + expect(requestBody.imp[0].banner.format[1].h).to.equal(600); + }); + + it('includes the detected referer in the bid request', function () { + const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; + expect(requestBody.site.page).to.equal('https://www.example.com/test'); + }); + + it('sets the banner pos correctly if sent', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + clonedBannerRequests[0].mediaTypes.banner.pos = 1; + + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.imp[0].banner.pos).to.equal(1); + }); + + it('sets the banner expansion direction correctly if sent', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + const expdir = [1, 3] + clonedBannerRequests[0].params.banner = { + expdir: expdir + }; + + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.imp[0].banner.expdir).to.equal(expdir); + }); + + it('sets keywords properly if sent', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + + config.setConfig({ortb2: { + site: { + keywords: 'highViewability, clothing, holiday shopping' + } + }}); + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + config.resetConfig(); + expect(requestBody.ext.ttdprebid.keywords).to.deep.equal(['highViewability', 'clothing', 'holiday shopping']); + }); + + it('sets ext properly', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.ext.ttdprebid.pbjs).to.equal('$prebid.version$'); + }); + + it('adds gdpr consent info to the request', function () { + let consentString = 'BON3G4EON3G4EAAABAENAA____ABl____A'; + let clonedBidderRequest = deepClone(baseBidderRequest); + clonedBidderRequest.gdprConsent = { + consentString: consentString, + gdprApplies: true + }; + + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + expect(requestBody.user.ext.consent).to.equal(consentString); + expect(requestBody.regs.ext.gdpr).to.equal(1); + }); + + it('adds usp consent info to the request', function () { + let consentString = 'BON3G4EON3G4EAAABAENAA____ABl____A'; + let clonedBidderRequest = deepClone(baseBidderRequest); + clonedBidderRequest.uspConsent = consentString; + + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + expect(requestBody.regs.ext.us_privacy).to.equal(consentString); + }); + + it('adds coppa consent info to the request', function () { + let clonedBidderRequest = deepClone(baseBidderRequest); + + config.setConfig({coppa: true}); + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + config.resetConfig(); + expect(requestBody.regs.coppa).to.equal(1); + }); + + it('adds schain info to the request', function () { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 1 + }] + }; + let clonedBannerBidRequests = deepClone(baseBannerBidRequests); + clonedBannerBidRequests[0].schain = schain; + + const requestBody = testBuildRequests(clonedBannerBidRequests, baseBidderRequest).data; + expect(requestBody.source.ext.schain).to.deep.equal(schain); + }); + + it('adds unified ID info to the request', function () { + const TDID = '00000000-0000-0000-0000-000000000000'; + let clonedBannerRequests = deepClone(baseBannerBidRequests); + clonedBannerRequests[0].userId = { + tdid: TDID + }; + + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.user.buyeruid).to.equal(TDID); + }); + + it('adds unified ID and UID2 info to user.ext.eids in the request', function () { + const TDID = '00000000-0000-0000-0000-000000000000'; + const UID2 = '99999999-9999-9999-9999-999999999999'; + let clonedBannerRequests = deepClone(baseBannerBidRequests); + clonedBannerRequests[0].userId = { + tdid: TDID, + uid2: { + id: UID2 + } + }; + const expectedEids = [ + { + source: 'adserver.org', + uids: [ + { + atype: 1, + ext: { + rtiPartner: 'TDID' + }, + id: TDID + } + ] + }, + { + source: 'uidapi.com', + uids: [ + { + atype: 3, + id: UID2 + } + ] + } + ]; + + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.user.ext.eids).to.deep.equal(expectedEids); + }); + + it('adds first party site data to the request', function () { + let clonedBidderRequest = deepClone(baseBidderRequest); + + config.setConfig({ortb2: { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'], + page: 'https://page.example.com/here.html', + ref: 'https://ref.example.com', + keywords: 'power tools, drills' + } + }}); + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + config.resetConfig(); + expect(requestBody.site.name).to.equal('example'); + expect(requestBody.site.domain).to.equal('page.example.com'); + expect(requestBody.site.cat[0]).to.equal('IAB2'); + expect(requestBody.site.sectioncat[0]).to.equal('IAB2-2'); + expect(requestBody.site.pagecat[0]).to.equal('IAB2-2'); + expect(requestBody.site.page).to.equal('https://page.example.com/here.html'); + expect(requestBody.site.ref).to.equal('https://ref.example.com'); + expect(requestBody.site.keywords).to.equal('power tools, drills'); + }); + }); + + describe('buildRequests-banner-multiple', function () { + const baseBannerMultipleBidRequests = [{ + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '13144370', + 'placementId': 'bottom' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'sizes': [[300, 250], [300, 600]], + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '8651474f-58b1-4368-b812-84f8c937a099', + 'bidId': 'small', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }, { + 'bidder': 'ttd', + 'params': { + 'publisherId': '13144370', + 'placementId': 'top', + 'siteId': 'testSite123' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + } + }, + 'sizes': [[728, 90]], + 'adUnitCode': 'div-gpt-ad-91515710-0', + 'transactionId': '825c1228-ca8c-4657-b40f-2df500621527', + 'bidId': 'large', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }]; + + const baseBidderRequest = { + 'bidderCode': 'ttd', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'bidderRequestId': '18084284054531', + 'auctionStart': 1540945362095, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'https://www.test.com', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://www.test.com' + ] + }, + 'start': 1540945362099, + 'doneCbCallCount': 0 + }; + + it('sends multiple impressions', function () { + const requestBody = testBuildRequests(baseBannerMultipleBidRequests, baseBidderRequest).data; + expect(requestBody.imp.length).to.equal(2); + }); + + it('sends the right tag ids for each ad unit', function () { + const requestBody = testBuildRequests(baseBannerMultipleBidRequests, baseBidderRequest).data; + requestBody.imp.forEach(imp => { + if (imp.id === 'small') { + expect(imp.tagid).to.equal('bottom'); + } else if (imp.id === 'large') { + expect(imp.tagid).to.equal('top'); + } else { + assert.fail('no matching impression id found'); + } + }); + }); + + it('sends the sizes for each ad unit', function () { + const requestBody = testBuildRequests(baseBannerMultipleBidRequests, baseBidderRequest).data; + requestBody.imp.forEach(imp => { + if (imp.id === 'small') { + expect(imp.banner.format[0].w).to.equal(300); + expect(imp.banner.format[0].h).to.equal(250); + expect(imp.banner.format[1].w).to.equal(300); + expect(imp.banner.format[1].h).to.equal(600); + } else if (imp.id === 'large') { + expect(imp.banner.format[0].w).to.equal(728); + expect(imp.banner.format[0].h).to.equal(90); + } else { + assert.fail('no matching impression id found'); + } + }); + }); + }); + + describe('buildRequests-display-video-multiformat', function () { + const baseMultiformatBidRequests = [{ + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '13144370', + 'placementId': '1gaa015' + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'api': [1, 3], + 'mimes': ['video/mp4'], + 'protocols': [2, 3, 5, 6], + 'minduration': 5, + 'maxduration': 30 + }, + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '8651474f-58b1-4368-b812-84f8c937a099', + 'bidId': '243310435309b5', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }]; + + const baseBidderRequest = { + 'bidderCode': 'ttd', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'bidderRequestId': '18084284054531', + 'auctionStart': 1540945362095, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'https://www.example.com/test', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://www.example.com/test' + ] + }, + 'start': 1540945362099, + 'doneCbCallCount': 0 + }; + + it('includes the video ad size in the bid request', function () { + const requestBody = testBuildRequests(baseMultiformatBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.w).to.equal(640); + expect(requestBody.imp[0].video.h).to.equal(480); + }); + + it('includes the banner ad size in the bid request', function () { + const requestBody = testBuildRequests(baseMultiformatBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].banner.format[0].w).to.equal(300); + expect(requestBody.imp[0].banner.format[0].h).to.equal(250); + expect(requestBody.imp[0].banner.format[1].w).to.equal(300); + expect(requestBody.imp[0].banner.format[1].h).to.equal(600); + }); + }); + + describe('buildRequests-video', function () { + const baseVideoBidRequests = [{ + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '13144370', + 'placementId': '1gaa015' + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'api': [1, 3], + 'mimes': ['video/mp4'], + 'protocols': [2, 3, 5, 6], + 'minduration': 5, + 'maxduration': 30 + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '8651474f-58b1-4368-b812-84f8c937a099', + 'bidId': '243310435309b5', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }]; + + const baseBidderRequest = { + 'bidderCode': 'ttd', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'bidderRequestId': '18084284054531', + 'auctionStart': 1540945362095, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'https://www.example.com/test', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://www.example.com/test' + ] + }, + 'start': 1540945362099, + 'doneCbCallCount': 0 + }; + + it('includes the ad size in the bid request', function () { + const requestBody = testBuildRequests(baseVideoBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.w).to.equal(640); + expect(requestBody.imp[0].video.h).to.equal(480); + }); + + it('includes the mimes in the bid request', function () { + const requestBody = testBuildRequests(baseVideoBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.mimes[0]).to.equal('video/mp4'); + }); + + it('includes the min and max duration in the bid request', function () { + const requestBody = testBuildRequests(baseVideoBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.minduration).to.equal(5); + expect(requestBody.imp[0].video.maxduration).to.equal(30); + }); + + it('sets the minduration to 0 if missing', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + delete clonedVideoRequests[0].mediaTypes.video.minduration + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.minduration).to.equal(0); + }); + + it('includes the api frameworks in the bid request', function () { + const requestBody = testBuildRequests(baseVideoBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.api[0]).to.equal(1); + expect(requestBody.imp[0].video.api[1]).to.equal(3); + }); + + it('includes the protocols in the bid request', function () { + const requestBody = testBuildRequests(baseVideoBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.protocols[0]).to.equal(2); + expect(requestBody.imp[0].video.protocols[1]).to.equal(3); + expect(requestBody.imp[0].video.protocols[2]).to.equal(5); + expect(requestBody.imp[0].video.protocols[3]).to.equal(6); + }); + + it('sets skip correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.skip = 1; + clonedVideoRequests[0].mediaTypes.video.skipmin = 5; + clonedVideoRequests[0].mediaTypes.video.skipafter = 10; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.skip).to.equal(1); + expect(requestBody.imp[0].video.skipmin).to.equal(5); + expect(requestBody.imp[0].video.skipafter).to.equal(10); + }); + + it('sets bitrate correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.minbitrate = 100; + clonedVideoRequests[0].mediaTypes.video.maxbitrate = 500; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.minbitrate).to.equal(100); + expect(requestBody.imp[0].video.maxbitrate).to.equal(500); + }); + + it('sets pos correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.pos = 1; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.pos).to.equal(1); + }); + + it('sets playbackmethod correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.playbackmethod = [1]; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.playbackmethod[0]).to.equal(1); + }); + + it('sets startdelay correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.startdelay = -1; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.startdelay).to.equal(-1); + }); + + it('sets placement correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.placement = 3; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.placement).to.equal(3); + }); + }); + + describe('interpretResponse-empty', function () { + it('should handle empty response', function () { + let result = spec.interpretResponse({}); + expect(result.length).to.equal(0); + }); + + it('should handle empty seatbid response', function () { + let response = { + body: { + 'id': '5e5c23a5ba71e78', + 'seatbid': [] + } + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('interpretResponse-simple-display', function () { + const incoming = { + body: { + 'id': '5e5c23a5ba71e78', + 'seatbid': [ + { + 'bid': [ + { + 'id': '6vmb3isptf', + 'crid': 'ttdscreative', + 'impid': '322add653672f68', + 'price': 1.22, + 'adm': '', + 'cat': [], + 'h': 90, + 'w': 728, + 'ttl': 60, + 'dealid': 'ttd-dealid-1', + 'adomain': ['advertiser.com'], + 'ext': { + 'mediatype': 1 + } + } + ], + 'seat': 'MOCK' + } + ], + 'cur': 'EUR', + 'bidid': '5e5c23a5ba71e78' + } + }; + + const serverRequest = { + 'method': 'POST', + 'url': 'https://direct.adsrvr.org/bid/bidder/supplier', + 'data': { + 'id': 'c47237df-c108-419f-9c2b-da513dc3c133', + 'imp': [ + { + 'id': '322add653672f68', + 'tagid': 'simple', + 'banner': { + 'w': 728, + 'h': 90, + 'format': [ + { + 'w': 728, + 'h': 90 + } + ] + } + } + ], + 'site': { + 'page': 'http://www.test.com', + 'publisher': { + 'id': '111' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36', + 'dnt': 0, + 'language': 'en-US', + 'connectiontype': 0 + }, + 'user': {}, + 'at': 1, + 'cur': [ + 'USD' + ], + 'regs': {}, + 'ext': { + 'ttdprebid': { + 'ver': 'TTD-PREBID-2019.11.12', + 'pbjs': '2.31.0' + } + } + }, + 'options': { + 'withCredentials': true + } + }; + + const expectedBid = { + 'requestId': '322add653672f68', + 'cpm': 1.22, + 'width': 728, + 'height': 90, + 'creativeId': 'ttdscreative', + 'dealId': 'ttd-dealid-1', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 60, + 'ad': '', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['advertiser.com'] + } + }; + + it('should get the correct bid response', function () { + let result = spec.interpretResponse(incoming, serverRequest); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedBid); + }); + }); + + describe('interpretResponse-multiple-display', function () { + const incoming = { + 'body': { + 'id': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'small', + 'impid': 'small', + 'price': 4.25, + 'adm': 'Default Test Ad Tag', + 'cid': 'campaignId132', + 'crid': 'creativeId999', + 'adomain': [ + 'http://foo' + ], + 'dealid': null, + 'w': 300, + 'h': 600, + 'cat': [], + 'ext': { + 'mediatype': 1 + } + }, + { + 'id': 'large', + 'impid': 'large', + 'price': 5.25, + 'adm': 'Default Test Ad Tag', + 'cid': 'campaignId132', + 'crid': 'creativeId222', + 'adomain': [ + 'http://foo2' + ], + 'dealid': null, + 'w': 728, + 'h': 90, + 'cat': [], + 'ext': { + 'mediatype': 1 + } + } + ], + 'seat': 'supplyVendorBuyerId132' + } + ], + 'cur': 'USD' + } + }; + + const expectedBids = [ + { + 'requestId': 'small', + 'cpm': 4.25, + 'width': 300, + 'height': 600, + 'creativeId': 'creativeId999', + 'currency': 'USD', + 'dealId': null, + 'netRevenue': true, + 'ttl': 360, + 'ad': 'Default Test Ad Tag', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['http://foo'] + } + }, + { + 'requestId': 'large', + 'cpm': 5.25, + 'width': 728, + 'height': 90, + 'creativeId': 'creativeId222', + 'currency': 'USD', + 'dealId': null, + 'netRevenue': true, + 'ttl': 360, + 'ad': 'Default Test Ad Tag', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['http://foo2'] + } + } + ]; + + const serverRequest = { + 'method': 'POST', + 'url': 'https://direct.adsrvr.org/bid/bidder/supplier', + 'data': { + 'id': 'c47237df-c108-419f-9c2b-da513dc3c133', + 'imp': [ + { + 'id': 'small', + 'tagid': 'test1', + 'banner': { + 'w': 300, + 'h': 600, + 'format': [ + { + 'w': 300, + 'h': 600 + } + ] + } + }, + { + 'id': 'large', + 'tagid': 'test2', + 'banner': { + 'w': 728, + 'h': 90, + 'format': [ + { + 'w': 728, + 'h': 90 + } + ] + } + } + ], + 'site': { + 'page': 'http://www.test.com', + 'publisher': { + 'id': '111' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36', + 'dnt': 0, + 'language': 'en-US', + 'connectiontype': 0 + }, + 'user': {}, + 'at': 1, + 'cur': [ + 'USD' + ], + 'regs': {}, + 'ext': { + 'ttdprebid': { + 'ver': 'TTD-PREBID-2019.11.12', + 'pbjs': '2.31.0' + } + } + }, + 'options': { + 'withCredentials': true + } + }; + + it('should get the correct bid response', function () { + let result = spec.interpretResponse(incoming, serverRequest); + expect(result.length).to.equal(2); + expect(result).to.deep.equal(expectedBids); + }); + }); + + describe('interpretResponse-simple-video', function () { + const incoming = { + 'body': { + 'cur': 'USD', + 'seatbid': [ + { + 'bid': [ + { + 'crid': 'mokivv6m', + 'ext': { + 'advid': '7ieo6xk', + 'agid': '7q9n3s2', + 'deal': { + 'dealid': '7013542' + }, + 'imptrackers': [], + 'viewabilityvendors': [], + 'mediatype': 2 + }, + 'h': 480, + 'impid': '2eabb87dfbcae4', + 'nurl': 'https://insight.adsrvr.org/enduser/vast?iid=00000000-0000-0000-0000-000000000000&crid=v3pek2eh&wp=${AUCTION_PRICE}&aid=&wpc=&sfe=0&puid=&tdid=00000000-0000-0000-0000-000000000000&pid=&ag=&adv=&sig=AAAAAAAAAAAAAA.&cf=&fq=0&td_s=&rcats=&mcat=&mste=&mfld=4&mssi=&mfsi=&uhow=&agsa=&rgco=&rgre=&rgme=&rgci=&rgz=&svbttd=0&dt=&osf=&os=&br=&rlangs=en&mlang=en&svpid=&did=&rcxt=&lat=&lon=&tmpc=&daid=&vp=0&osi=&osv=&dc=0&vcc=QAFIAVABiAECwAEDyAED0AED6AEG8AEBgAIDigIMCAIIBQgDCAYICwgMmgIECAEIAqACA6gCAsACAA..&sv=noop&pidi=&advi=&cmpi=&agi=&cridi=&svi=&cmp=&skip=1&c=&dur=&crrelr=', + 'price': 13.6, + 'ttl': 500, + 'w': 600 + } + ], + 'seat': 'supplyVendorBuyerId132' + } + ] + }, + 'headers': {} + }; + + const serverRequest = { + 'method': 'POST', + 'url': 'https://direct.bid.adsrvr.org/bid/bidder/supplier', + 'data': { + 'id': 'e94ec12d-ae1d-4ed7-abd1-eb3198ce3b63', + 'imp': [ + { + 'id': '2eabb87dfbcae4', + 'tagid': 'video', + 'video': { + 'api': [ + 1, + 3 + ], + 'mimes': [ + 'video/mp4' + ], + 'minduration': 5, + 'maxduration': 30, + 'w': 640, + 'h': 480, + 'protocols': [ + 2, + 3, + 5, + 6 + ] + } + } + ], + 'site': { + 'page': 'http://www.test.com', + 'publisher': { + 'id': '111' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', + 'dnt': 0, + 'language': 'en-US', + 'connectiontype': 0 + }, + 'user': {}, + 'at': 1, + 'cur': [ + 'USD' + ], + 'regs': {}, + 'ext': { + 'ttdprebid': { + 'ver': 'TTD-PREBID-2019.11.12', + 'pbjs': '3.10.0' + } + } + }, + 'options': { + 'withCredentials': true + } + }; + + const expectedBid = + { + 'requestId': '2eabb87dfbcae4', + 'cpm': 13.6, + 'creativeId': 'mokivv6m', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 500, + 'width': 640, + 'height': 480, + 'mediaType': 'video', + 'vastUrl': 'https://insight.adsrvr.org/enduser/vast?iid=00000000-0000-0000-0000-000000000000&crid=v3pek2eh&wp=13.6&aid=&wpc=&sfe=0&puid=&tdid=00000000-0000-0000-0000-000000000000&pid=&ag=&adv=&sig=AAAAAAAAAAAAAA.&cf=&fq=0&td_s=&rcats=&mcat=&mste=&mfld=4&mssi=&mfsi=&uhow=&agsa=&rgco=&rgre=&rgme=&rgci=&rgz=&svbttd=0&dt=&osf=&os=&br=&rlangs=en&mlang=en&svpid=&did=&rcxt=&lat=&lon=&tmpc=&daid=&vp=0&osi=&osv=&dc=0&vcc=QAFIAVABiAECwAEDyAED0AED6AEG8AEBgAIDigIMCAIIBQgDCAYICwgMmgIECAEIAqACA6gCAsACAA..&sv=noop&pidi=&advi=&cmpi=&agi=&cridi=&svi=&cmp=&skip=1&c=&dur=&crrelr=', + 'meta': {} + }; + + it('should get the correct bid response if nurl is returned', function () { + let result = spec.interpretResponse(incoming, serverRequest); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedBid); + }); + + it('should get the correct bid response if adm is returned', function () { + const vastXml = "2.0574840600:00:30 \"Click ]]>"; + let admIncoming = deepClone(incoming); + delete admIncoming.body.seatbid[0].bid[0].nurl; + admIncoming.body.seatbid[0].bid[0].adm = vastXml; + + let vastXmlExpectedBid = deepClone(expectedBid); + delete vastXmlExpectedBid.vastUrl; + vastXmlExpectedBid.vastXml = vastXml; + + let result = spec.interpretResponse(admIncoming, serverRequest); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(vastXmlExpectedBid); + }); + }); + + describe('interpretResponse-display-and-video', function () { + const incoming = { + 'body': { + 'id': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'small', + 'impid': 'small', + 'price': 4.25, + 'adm': 'Default Test Ad Tag', + 'cid': 'campaignId132', + 'crid': 'creativeId999', + 'adomain': [ + 'http://foo' + ], + 'dealid': null, + 'w': 300, + 'h': 600, + 'cat': [], + 'ext': { + 'mediatype': 1 + } + }, + { + 'crid': 'mokivv6m', + 'ext': { + 'advid': '7ieo6xk', + 'agid': '7q9n3s2', + 'deal': { + 'dealid': '7013542' + }, + 'imptrackers': [], + 'viewabilityvendors': [], + 'mediatype': 2 + }, + 'h': 480, + 'impid': '2eabb87dfbcae4', + 'nurl': 'https://insight.adsrvr.org/enduser/vast?iid=00000000-0000-0000-0000-000000000000&crid=v3pek2eh&wp=${AUCTION_PRICE}&aid=&wpc=&sfe=0&puid=&tdid=00000000-0000-0000-0000-000000000000&pid=&ag=&adv=&sig=AAAAAAAAAAAAAA.&cf=&fq=0&td_s=&rcats=&mcat=&mste=&mfld=4&mssi=&mfsi=&uhow=&agsa=&rgco=&rgre=&rgme=&rgci=&rgz=&svbttd=0&dt=&osf=&os=&br=&rlangs=en&mlang=en&svpid=&did=&rcxt=&lat=&lon=&tmpc=&daid=&vp=0&osi=&osv=&dc=0&vcc=QAFIAVABiAECwAEDyAED0AED6AEG8AEBgAIDigIMCAIIBQgDCAYICwgMmgIECAEIAqACA6gCAsACAA..&sv=noop&pidi=&advi=&cmpi=&agi=&cridi=&svi=&cmp=&skip=1&c=&dur=&crrelr=', + 'price': 13.6, + 'ttl': 500, + 'w': 600 + } + ], + 'seat': 'supplyVendorBuyerId132' + } + ], + 'cur': 'USD' + } + }; + + const expectedBids = [ + { + 'requestId': 'small', + 'cpm': 4.25, + 'width': 300, + 'height': 600, + 'creativeId': 'creativeId999', + 'currency': 'USD', + 'dealId': null, + 'netRevenue': true, + 'ttl': 360, + 'ad': 'Default Test Ad Tag', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['http://foo'] + } + }, + { + 'requestId': '2eabb87dfbcae4', + 'cpm': 13.6, + 'creativeId': 'mokivv6m', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 500, + 'width': 640, + 'height': 480, + 'mediaType': 'video', + 'vastUrl': 'https://insight.adsrvr.org/enduser/vast?iid=00000000-0000-0000-0000-000000000000&crid=v3pek2eh&wp=13.6&aid=&wpc=&sfe=0&puid=&tdid=00000000-0000-0000-0000-000000000000&pid=&ag=&adv=&sig=AAAAAAAAAAAAAA.&cf=&fq=0&td_s=&rcats=&mcat=&mste=&mfld=4&mssi=&mfsi=&uhow=&agsa=&rgco=&rgre=&rgme=&rgci=&rgz=&svbttd=0&dt=&osf=&os=&br=&rlangs=en&mlang=en&svpid=&did=&rcxt=&lat=&lon=&tmpc=&daid=&vp=0&osi=&osv=&dc=0&vcc=QAFIAVABiAECwAEDyAED0AED6AEG8AEBgAIDigIMCAIIBQgDCAYICwgMmgIECAEIAqACA6gCAsACAA..&sv=noop&pidi=&advi=&cmpi=&agi=&cridi=&svi=&cmp=&skip=1&c=&dur=&crrelr=', + 'meta': {} + } + ]; + + const serverRequest = { + 'method': 'POST', + 'url': 'https://direct.adsrvr.org/bid/bidder/supplier', + 'data': { + 'id': 'c47237df-c108-419f-9c2b-da513dc3c133', + 'imp': [ + { + 'id': 'small', + 'tagid': 'test1', + 'banner': { + 'w': 300, + 'h': 600, + 'format': [ + { + 'w': 300, + 'h': 600 + } + ] + }, + }, + { + 'id': '2eabb87dfbcae4', + 'tagid': 'video', + 'video': { + 'api': [ + 1, + 3 + ], + 'mimes': [ + 'video/mp4' + ], + 'minduration': 5, + 'maxduration': 30, + 'w': 640, + 'h': 480, + 'protocols': [ + 2, + 3, + 5, + 6 + ] + } + } + ], + 'site': { + 'page': 'http://www.test.com', + 'publisher': { + 'id': '111' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36', + 'dnt': 0, + 'language': 'en-US', + 'connectiontype': 0 + }, + 'user': {}, + 'at': 1, + 'cur': [ + 'USD' + ], + 'regs': {}, + 'ext': { + 'ttdprebid': { + 'ver': 'TTD-PREBID-2019.11.12', + 'pbjs': '2.31.0' + } + } + }, + 'options': { + 'withCredentials': true + } + }; + + it('should get the correct bid response', function () { + let result = spec.interpretResponse(incoming, serverRequest); + expect(result.length).to.equal(2); + expect(result).to.deep.equal(expectedBids); + }); + }); +}); diff --git a/test/spec/modules/turktelekomBidAdapter_spec.js b/test/spec/modules/turktelekomBidAdapter_spec.js deleted file mode 100644 index c4e55178638..00000000000 --- a/test/spec/modules/turktelekomBidAdapter_spec.js +++ /dev/null @@ -1,749 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/turktelekomBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -describe('TurkTelekomAdapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'turktelekom', - 'params': { - 'uid': '17' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'uid': 0 - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - function parseRequest(url) { - const res = {}; - url.split('&').forEach((it) => { - const couple = it.split('='); - res[couple[0]] = decodeURIComponent(couple[1]); - }); - return res; - } - - const bidderRequest = { - refererInfo: { - referer: 'https://example.com' - } - }; - const referrer = bidderRequest.refererInfo.referer; - - let bidRequests = [ - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '18' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '18' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90], [300, 250]], - 'bidId': '3150ccb55da321', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '20' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '42dbe3a7168a6a', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - it('should attach valid params to the tag', function () { - const request = spec.buildRequests([bidRequests[0]], bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '18'); - expect(payload).to.have.property('sizes', '300x250,300x600'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - expect(payload).to.have.property('wrapperType', 'Prebid_js'); - expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); - }); - - it('sizes must not be duplicated', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '18,18,20'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - }); - - it('pt parameter must be "gross" if params.priceType === "gross"', function () { - bidRequests[1].params.priceType = 'gross'; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'gross'); - expect(payload).to.have.property('auids', '18,18,20'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - delete bidRequests[1].params.priceType; - }); - - it('pt parameter must be "net" or "gross"', function () { - bidRequests[1].params.priceType = 'some'; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '18,18,20'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - delete bidRequests[1].params.priceType; - }); - - it('if gdprConsent is present payload must have gdpr params', function () { - const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '1'); - }); - - it('if gdprApplies is false gdpr_applies must be 0', function () { - const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: false}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '0'); - }); - - it('if gdprApplies is undefined gdpr_applies must be 1', function () { - const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA'}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '1'); - }); - }); - - describe('interpretResponse', function () { - const responses = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 17, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 18, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 17, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'price': 0, 'auid': 19, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0, 'adm': '
test content 5
', 'h': 250, 'w': 300}], 'seat': '1'}, - undefined, - {'bid': [], 'seat': '1'}, - {'seat': '1'}, - ]; - - it('should get correct bid response', function () { - const bidRequests = [ - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '17' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '659423fff799cb', - 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', - } - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '659423fff799cb', - 'cpm': 1.15, - 'creativeId': 17, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 1
', - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); - expect(result).to.deep.equal(expectedResponse); - }); - - it('should get correct multi bid response', function () { - const bidRequests = [ - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '17' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '300bfeb0d71a5b', - 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '18' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4dff80cc4ee346', - 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '17' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'bidId': '5703af74d0472a', - 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', - } - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '300bfeb0d71a5b', - 'cpm': 1.15, - 'creativeId': 17, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 1
', - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '4dff80cc4ee346', - 'cpm': 0.5, - 'creativeId': 18, - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'ad': '
test content 2
', - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '5703af74d0472a', - 'cpm': 0.15, - 'creativeId': 17, - 'dealId': undefined, - 'width': 728, - 'height': 90, - 'ad': '
test content 3
', - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': responses.slice(0, 3)}}, request); - expect(result).to.deep.equal(expectedResponse); - }); - - it('handles wrong and nobid responses', function () { - const bidRequests = [ - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '19' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '300bfeb0d7190gf', - 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '20' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '300bfeb0d71321', - 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '25' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'bidId': '300bfeb0d7183bb', - 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', - } - ]; - const request = spec.buildRequests(bidRequests); - const result = spec.interpretResponse({'body': {'seatbid': responses.slice(3)}}, request); - expect(result.length).to.equal(0); - }); - - it('complicated case', function () { - const fullResponse = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 17, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 18, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 17, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 4
', 'auid': 17, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 5
', 'auid': 18, 'h': 600, 'w': 350}], 'seat': '1'}, - ]; - const bidRequests = [ - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '17' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '2164be6358b9', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '17' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '326bde7fbf69', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '18' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4e111f1b66e4', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '17' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'bidId': '26d6f897b516', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '44' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'bidId': '1751cd90161', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - } - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '2164be6358b9', - 'cpm': 1.15, - 'creativeId': 17, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 1
', - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '4e111f1b66e4', - 'cpm': 0.5, - 'creativeId': 18, - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'ad': '
test content 2
', - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '26d6f897b516', - 'cpm': 0.15, - 'creativeId': 17, - 'dealId': undefined, - 'width': 728, - 'height': 90, - 'ad': '
test content 3
', - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '326bde7fbf69', - 'cpm': 0.15, - 'creativeId': 17, - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'ad': '
test content 4
', - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); - expect(result).to.deep.equal(expectedResponse); - }); - - it('dublicate uids and sizes in one slot', function () { - const fullResponse = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 17, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 17, 'h': 250, 'w': 300}], 'seat': '1'}, - ]; - const bidRequests = [ - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '17' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '5126e301f4be', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '17' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '57b2ebe70e16', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '17' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '225fcd44b18c', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', - } - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '5126e301f4be', - 'cpm': 1.15, - 'creativeId': 17, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 1
', - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - }, - { - 'requestId': '57b2ebe70e16', - 'cpm': 0.5, - 'creativeId': 17, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 2
', - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); - expect(result).to.deep.equal(expectedResponse); - }); - }); - - it('should get correct video bid response', function () { - const bidRequests = [ - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '25' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '57dfefb80eca', - 'bidderRequestId': '20394420a762a2', - 'auctionId': '140132d07b031', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '26' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'e893c787c22dd', - 'bidderRequestId': '20394420a762a2', - 'auctionId': '140132d07b031', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - } - ]; - const response = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 25, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, - {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 26, content_type: 'video'}], 'seat': '2'} - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '57dfefb80eca', - 'cpm': 1.15, - 'creativeId': 25, - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - } - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': response}}, request); - expect(result).to.deep.equal(expectedResponse); - }); - - it('should have right renderer in the bid response', function () { - const spySetRenderer = sinon.spy(); - const stubRenderer = { - setRender: spySetRenderer - }; - const spyRendererInstall = sinon.spy(function() { return stubRenderer; }); - const stubRendererConst = { - install: spyRendererInstall - }; - const bidRequests = [ - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '25' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'e6e65553fc8', - 'bidderRequestId': '1380f393215dc7', - 'auctionId': '10b8d2f3c697e3', - 'mediaTypes': { - 'video': { - 'context': 'outstream' - } - } - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '26' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c8fdcb3f269f', - 'bidderRequestId': '1380f393215dc7', - 'auctionId': '10b8d2f3c697e3' - }, - { - 'bidder': 'turktelekom', - 'params': { - 'uid': '27' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '1de036c37685', - 'bidderRequestId': '1380f393215dc7', - 'auctionId': '10b8d2f3c697e3', - 'renderer': {} - } - ]; - const response = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 25, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, - {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 26, content_type: 'video', w: 300, h: 250}], 'seat': '2'}, - {'bid': [{'price': 1.20, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 27, content_type: 'video', w: 300, h: 250}], 'seat': '2'} - ]; - const request = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': 'e6e65553fc8', - 'cpm': 1.15, - 'creativeId': 25, - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - }, - 'renderer': stubRenderer - }, - { - 'requestId': 'c8fdcb3f269f', - 'cpm': 1.00, - 'creativeId': 26, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - }, - 'renderer': stubRenderer - }, - { - 'requestId': '1de036c37685', - 'cpm': 1.20, - 'creativeId': 27, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'bidderCode': 'turktelekom', - 'currency': 'TRY', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - } - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': response}}, request, stubRendererConst); - - expect(spySetRenderer.calledTwice).to.equal(true); - expect(spySetRenderer.getCall(0).args[0]).to.be.a('function'); - expect(spySetRenderer.getCall(1).args[0]).to.be.a('function'); - - expect(spyRendererInstall.calledTwice).to.equal(true); - expect(spyRendererInstall.getCall(0).args[0]).to.deep.equal({ - id: 'e6e65553fc8', - url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', - loaded: false - }); - expect(spyRendererInstall.getCall(1).args[0]).to.deep.equal({ - id: 'c8fdcb3f269f', - url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', - loaded: false - }); - - expect(result).to.deep.equal(expectedResponse); - }); -}); diff --git a/test/spec/modules/ucfunnelBidAdapter_spec.js b/test/spec/modules/ucfunnelBidAdapter_spec.js index d7e82338ff3..ac788e537e2 100644 --- a/test/spec/modules/ucfunnelBidAdapter_spec.js +++ b/test/spec/modules/ucfunnelBidAdapter_spec.js @@ -14,17 +14,17 @@ const userId = { 'netId': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', 'parrableId': {'eid': '01.1608624401.fe44bca9b96873084a0d4e9d0ac5729f13790ba8f8e58fa4707b6b3c096df91c6b5f254992bdad4ab1dd4a89919081e9b877d7a039ac3183709277665bac124f28e277d109f0ff965058'}, 'pubcid': 'd8aa10fa-d86c-451d-aad8-5f16162a9e64', - 'sharedid': {'id': '01ESHXW4HD29KMF387T63JQ9H5', 'third': '01ESHXW4HD29KMF387T63JQ9H5'}, 'tdid': 'D6885E90-2A7A-4E0F-87CB-7734ED1B99A3', 'haloId': {}, - 'uid2': {'id': 'eb33b0cb-8d35-4722-b9c0-1a31d4064888'} + 'uid2': {'id': 'eb33b0cb-8d35-4722-b9c0-1a31d4064888'}, + 'flocId': {'id': '12144', 'version': 'chrome.1.1'}, + 'connectid': '4567' } const validBannerBidReq = { bidder: BIDDER_CODE, params: { - adid: 'ad-34BBD2AA24B678BBFD4E7B9EE3B872D', - bidfloor: 1.0 + adid: 'ad-34BBD2AA24B678BBFD4E7B9EE3B872D' }, sizes: [[300, 250]], bidId: '263be71e91dd9d', @@ -159,7 +159,8 @@ describe('ucfunnel Adapter', function () { expect(data.adid).to.equal('ad-34BBD2AA24B678BBFD4E7B9EE3B872D'); expect(data.w).to.equal(width); expect(data.h).to.equal(height); - expect(data.eids).to.equal('uid2,eb33b0cb-8d35-4722-b9c0-1a31d4064888'); + expect(data.eids).to.equal('uid2,eb33b0cb-8d35-4722-b9c0-1a31d4064888!verizonMediaId,4567'); + expect(data.cid).to.equal('12144'); expect(data.schain).to.equal('1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com'); }); @@ -172,6 +173,41 @@ describe('ucfunnel Adapter', function () { expect(data.w).to.equal(width); expect(data.h).to.equal(height); }); + + it('should set bidfloor if configured', function() { + let bid = Object.assign({}, validBannerBidReq); + bid.getFloor = function() { + return { + currency: 'USD', + floor: 2.02 + } + }; + const requests = spec.buildRequests([ bid ]); + const data = requests[0].data; + expect(data.fp).to.equal(2.02); + }); + + it('should set bidfloor if configured', function() { + let bid = Object.assign({}, validBannerBidReq); + bid.params.bidfloor = 2.01; + const requests = spec.buildRequests([ bid ]); + const data = requests[0].data; + expect(data.fp).to.equal(2.01); + }); + + it('should set bidfloor if configured', function() { + let bid = Object.assign({}, validBannerBidReq); + bid.getFloor = function() { + return { + currency: 'USD', + floor: 2.02 + } + }; + bid.params.bidfloor = 2.01; + const requests = spec.buildRequests([ bid ]); + const data = requests[0].data; + expect(data.fp).to.equal(2.01); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/underdogmediaBidAdapter_spec.js b/test/spec/modules/underdogmediaBidAdapter_spec.js index aeb9f56c851..70d09513f27 100644 --- a/test/spec/modules/underdogmediaBidAdapter_spec.js +++ b/test/spec/modules/underdogmediaBidAdapter_spec.js @@ -276,6 +276,7 @@ describe('UnderdogMedia adapter', function () { mids: [ { ad_code_html: 'ad_code_html', + advertiser_domains: ['domain1'], cpm: 2.5, height: '600', mid: '32634', @@ -300,6 +301,7 @@ describe('UnderdogMedia adapter', function () { expect(bids).to.be.lengthOf(2); + expect(bids[0].meta.advertiserDomains).to.deep.equal(['domain1']) expect(bids[0].bidderCode).to.equal('underdogmedia'); expect(bids[0].cpm).to.equal(2.5); expect(bids[0].width).to.equal('160'); diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index 0faa321be5f..c24f63c0b99 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -1,5 +1,7 @@ -import { expect } from 'chai'; -import { spec } from 'modules/undertoneBidAdapter.js'; +import {expect} from 'chai'; +import {spec} from 'modules/undertoneBidAdapter.js'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {deepClone} from '../../../src/utils'; const URL = 'https://hb.undertone.com/hb'; const BIDDER_CODE = 'undertone'; @@ -60,6 +62,23 @@ const videoBidReq = [{ bidId: '263be71e91dd9d', auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' }]; +const schainObj = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] +}; const bidReq = [{ adUnitCode: 'div-gpt-ad-1460505748561-0', bidder: BIDDER_CODE, @@ -72,6 +91,29 @@ const bidReq = [{ auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' }, { + adUnitCode: 'div-gpt-ad-1460505748561-0', + bidder: BIDDER_CODE, + params: { + publisherId: 12345 + }, + sizes: [[1, 1]], + bidId: '453cf42d72bb3c', + auctionId: '6c22f5a5-59df-4dc6-b92c-f433bcf0a874', + schain: schainObj +}]; + +const supplyChainedBidReqs = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + bidder: BIDDER_CODE, + params: { + placementId: '10433394', + publisherId: 12345, + }, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746', + schain: schainObj +}, { adUnitCode: 'div-gpt-ad-1460505748561-0', bidder: BIDDER_CODE, params: { @@ -236,6 +278,69 @@ describe('Undertone Adapter', () => { sandbox.restore(); }); + describe('getFloor', function () { + it('should send 0 floor when getFloor is undefined', function() { + const request = spec.buildRequests(videoBidReq, bidderReq); + const bidReq = JSON.parse(request.data)['x-ut-hb-params'][0]; + expect(bidReq.mediaType).to.deep.equal(VIDEO); + expect(bidReq.bidfloor).to.deep.equal(0); + }); + it('should send mocked floor when defined on video media-type', function() { + const clonedVideoBidReqArr = deepClone(videoBidReq); + const mockedFloorResponse = { + currency: 'USD', + floor: 2.3 + }; + clonedVideoBidReqArr[1].getFloor = () => mockedFloorResponse; + + const request = spec.buildRequests(clonedVideoBidReqArr, bidderReq); + const bidReq1 = JSON.parse(request.data)['x-ut-hb-params'][0]; + const bidReq2 = JSON.parse(request.data)['x-ut-hb-params'][1]; + expect(bidReq1.mediaType).to.deep.equal(VIDEO); + expect(bidReq1.bidfloor).to.deep.equal(0); + + expect(bidReq2.mediaType).to.deep.equal(VIDEO); + expect(bidReq2.bidfloor).to.deep.equal(mockedFloorResponse.floor); + }); + it('should send mocked floor on banner media-type', function() { + const clonedValidBidReqArr = [deepClone(validBidReq)]; + const mockedFloorResponse = { + currency: 'USD', + floor: 2.3 + }; + clonedValidBidReqArr[0].getFloor = () => mockedFloorResponse; + + const request = spec.buildRequests(clonedValidBidReqArr, bidderReq); + const bidReq = JSON.parse(request.data)['x-ut-hb-params'][0]; + expect(bidReq.mediaType).to.deep.equal(BANNER); + expect(bidReq.bidfloor).to.deep.equal(mockedFloorResponse.floor); + }); + it('should send 0 floor on invalid currency', function() { + const clonedValidBidReqArr = [deepClone(validBidReq)]; + const mockedFloorResponse = { + currency: 'EUR', + floor: 2.3 + }; + clonedValidBidReqArr[0].getFloor = () => mockedFloorResponse; + + const request = spec.buildRequests(clonedValidBidReqArr, bidderReq); + const bidReq = JSON.parse(request.data)['x-ut-hb-params'][0]; + expect(bidReq.mediaType).to.deep.equal(BANNER); + expect(bidReq.bidfloor).to.deep.equal(0); + }); + }); + describe('supply chain', function () { + it('should send supply chain if found on first bid', function () { + const request = spec.buildRequests(supplyChainedBidReqs, bidderReq); + const commons = JSON.parse(request.data)['commons']; + expect(commons.schain).to.deep.equal(schainObj); + }); + it('should not send supply chain if not found on first bid', function () { + const request = spec.buildRequests(bidReq, bidderReq); + const commons = JSON.parse(request.data)['commons']; + expect(commons.schain).to.not.exist; + }); + }); it('should send request to correct url via POST not in GDPR or CCPA', function () { const request = spec.buildRequests(bidReq, bidderReq); const domainStart = bidderReq.refererInfo.referer.indexOf('//'); diff --git a/test/spec/modules/unicornBidAdapter_spec.js b/test/spec/modules/unicornBidAdapter_spec.js index dcd446b2bb0..1ab428d58b6 100644 --- a/test/spec/modules/unicornBidAdapter_spec.js +++ b/test/spec/modules/unicornBidAdapter_spec.js @@ -1,22 +1,35 @@ -import { assert, expect } from 'chai'; -import { spec } from 'modules/unicornBidAdapter.js'; +import {assert, expect} from 'chai'; +import {spec} from 'modules/unicornBidAdapter.js'; import * as _ from 'lodash'; const bidRequests = [ { bidder: 'unicorn', params: { - bidfloorCpm: 0, accountId: 12345 }, mediaTypes: { banner: { - sizes: [[300, 250], [336, 280]] + sizes: [ + [ + 300, 250 + ], + [ + 336, 280 + ] + ] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'ea0aa332-a6e1-4474-8180-83720e6b87bc', - sizes: [[300, 250], [336, 280]], + sizes: [ + [ + 300, 250 + ], + [ + 336, 280 + ] + ], bidId: '226416e6e6bf41', bidderRequestId: '1f41cbdcbe58d5', auctionId: '77987c3a-9be9-4e43-985a-26fc91d84724', @@ -24,20 +37,22 @@ const bidRequests = [ bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0 - }, - { + }, { bidder: 'unicorn', params: { - bidfloorCpm: 0, accountId: 12345 }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [ + [300, 250] + ] } }, transactionId: 'cf801303-cf98-4b4a-9e0a-c27b93bce6d8', - sizes: [[300, 250]], + sizes: [ + [300, 250] + ], bidId: '37cdc0b5d0363b', bidderRequestId: '1f41cbdcbe58d5', auctionId: '77987c3a-9be9-4e43-985a-26fc91d84724', @@ -45,20 +60,22 @@ const bidRequests = [ bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0 - }, - { + }, { bidder: 'unicorn', params: { - bidfloorCpm: 0 }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [ + [300, 250] + ] } }, adUnitCode: '/19968336/header-bid-tag-2', transactionId: 'ba7f114c-3676-4a08-a26d-1ee293d521ed', - sizes: [[300, 250]], + sizes: [ + [300, 250] + ], bidId: '468569a6597a4', bidderRequestId: '1f41cbdcbe58d5', auctionId: '77987c3a-9be9-4e43-985a-26fc91d84724', @@ -74,19 +91,32 @@ const validBidRequests = [ bidder: 'unicorn', params: { placementId: 'rectangle-ad-1', - bidfloorCpm: 0, accountId: 12345, publisherId: 99999, mediaId: 'example' }, mediaTypes: { banner: { - sizes: [[300, 250], [336, 280]] + sizes: [ + [ + 300, 250 + ], + [ + 336, 280 + ] + ] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'fbf94ccf-f377-4201-a662-32c2feb8ab6d', - sizes: [[300, 250], [336, 280]], + sizes: [ + [ + 300, 250 + ], + [ + 336, 280 + ] + ], bidId: '2fb90842443e24', bidderRequestId: '123ae4cc3eeb7e', auctionId: 'c594a888-6744-46c6-8b0e-d188e40e83ef', @@ -94,21 +124,23 @@ const validBidRequests = [ bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0 - }, - { + }, { bidder: 'unicorn', params: { - bidfloorCpm: 0, accountId: 12345 }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [ + [300, 250] + ] } }, adUnitCode: '/19968336/header-bid-tag-1', transactionId: '2d65e313-f8a6-4888-b9ab-50fb3ca744ea', - sizes: [[300, 250]], + sizes: [ + [300, 250] + ], bidId: '352f86f158d97a', bidderRequestId: '123ae4cc3eeb7e', auctionId: 'c594a888-6744-46c6-8b0e-d188e40e83ef', @@ -116,22 +148,24 @@ const validBidRequests = [ bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0 - }, - { + }, { bidder: 'unicorn', params: { placementId: 'rectangle-ad-2', - bidfloorCpm: 0, accountId: 12345 }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [ + [300, 250] + ] } }, adUnitCode: '/19968336/header-bid-tag-2', transactionId: '82f445a8-44bc-40bc-9913-739b40375566', - sizes: [[300, 250]], + sizes: [ + [300, 250] + ], bidId: '4cde82cc90126b', bidderRequestId: '123ae4cc3eeb7e', auctionId: 'c594a888-6744-46c6-8b0e-d188e40e83ef', @@ -151,17 +185,30 @@ const bidderRequest = { bidder: 'unicorn', params: { placementId: 'rectangle-ad-1', - bidfloorCpm: 0, accountId: 12345 }, mediaTypes: { banner: { - sizes: [[300, 250], [336, 280]] + sizes: [ + [ + 300, 250 + ], + [ + 336, 280 + ] + ] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'fbf94ccf-f377-4201-a662-32c2feb8ab6d', - sizes: [[300, 250], [336, 280]], + sizes: [ + [ + 300, 250 + ], + [ + 336, 280 + ] + ], bidId: '2fb90842443e24', bidderRequestId: '123ae4cc3eeb7e', auctionId: 'c594a888-6744-46c6-8b0e-d188e40e83ef', @@ -169,21 +216,23 @@ const bidderRequest = { bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0 - }, - { + }, { bidder: 'unicorn', params: { - bidfloorCpm: 0, accountId: 12345 }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [ + [300, 250] + ] } }, adUnitCode: '/19968336/header-bid-tag-1', transactionId: '2d65e313-f8a6-4888-b9ab-50fb3ca744ea', - sizes: [[300, 250]], + sizes: [ + [300, 250] + ], bidId: '352f86f158d97a', bidderRequestId: '123ae4cc3eeb7e', auctionId: 'c594a888-6744-46c6-8b0e-d188e40e83ef', @@ -191,22 +240,24 @@ const bidderRequest = { bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0 - }, - { + }, { bidder: 'unicorn', params: { placementId: 'rectangle-ad-2', - bidfloorCpm: 0, accountId: 12345 }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [ + [300, 250] + ] } }, adUnitCode: '/19968336/header-bid-tag-2', transactionId: '82f445a8-44bc-40bc-9913-739b40375566', - sizes: [[300, 250]], + sizes: [ + [300, 250] + ], bidId: '4cde82cc90126b', bidderRequestId: '123ae4cc3eeb7e', auctionId: 'c594a888-6744-46c6-8b0e-d188e40e83ef', @@ -240,8 +291,7 @@ const openRTBRequest = { { w: 300, h: 250 - }, - { + }, { w: 336, h: 280 } @@ -250,8 +300,7 @@ const openRTBRequest = { secure: 1, bidfloor: 0, tagid: 'rectangle-ad-1' - }, - { + }, { id: '31e2b28ced2475', banner: { w: 300, @@ -266,8 +315,7 @@ const openRTBRequest = { secure: 1, bidfloor: 0, tagid: '/19968336/header-bid-tag-1' - }, - { + }, { id: '40a333e047a9bd', banner: { w: 300, @@ -284,14 +332,14 @@ const openRTBRequest = { tagid: 'rectangle-ad-2' } ], - cur: 'JPY', + cur: ['JPY'], ext: { accountId: 12345 }, site: { id: 'example', publisher: { - id: 99999 + id: '99999' }, domain: 'uni-corn.net', page: 'https://uni-corn.net/', @@ -299,8 +347,7 @@ const openRTBRequest = { }, device: { language: 'ja', - ua: - 'Mozilla/5.0 (Linux; Android 8.0.0; ONEPLUS A5000) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36' + ua: 'Mozilla/5.0 (Linux; Android 8.0.0; ONEPLUS A5000) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36' }, user: { id: '69d9e1c2-801e-4901-a665-fad467550fec' @@ -310,7 +357,7 @@ const openRTBRequest = { ext: { stype: 'prebid_uncn', bidder: 'unicorn', - prebid_version: '1.0' + prebid_version: '1.1' } } }; @@ -342,11 +389,10 @@ const serverResponse = { iurl: 'https://assets.ucontent.net/test1.jpg', price: 1.0017, w: 300 - }, - { + }, { adid: 'uqgbp4y0_uqjrNT7h_25512', adm: '
test
', - adomain: ['test1.co.jp'], + adomain: null, attr: ['6'], bundle: 'com.test1.android', cat: ['IAB9'], @@ -365,13 +411,11 @@ const serverResponse = { ], group: 0, seat: '65' - }, - { + }, { bid: [ { adid: 'uoNYC6II_eoySuXNi', adm: '
test
', - adomain: ['test2.co.jp'], attr: [], bundle: 'jp.co.test2', cat: ['IAB9'], @@ -400,8 +444,7 @@ const serverResponse = { const request = { method: 'POST', url: 'https://ds.uncn.jp/pb/0/bid.json', - data: - '{"id":"5ebea288-f13a-4754-be6d-4ade66c68877","at":1,"imp":[{"id":"216255f234b602","banner":{"w":300,"h":250},"format":[{"w":300,"h":250},{"w":336,"h":280}],"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-0"},{"id":"31e2b28ced2475","banner":{"w":"300","h":"250"},"format":[{"w":"300","h":"250"}],"secure":1,"bidfloor":0"tagid":"/19968336/header-bid-tag-1"},{"id":"40a333e047a9bd","banner":{"w":300,"h":250},"format":[{"w":300,"h":250}],"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-2"}],"cur":"JPY","site":{"id":"uni-corn.net","publisher":{"id":12345},"domain":"uni-corn.net","page":"https://uni-corn.net/","ref":"https://uni-corn.net/"},"device":{"language":"ja","ua":"Mozilla/5.0 (Linux; Android 8.0.0; ONEPLUS A5000) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36"},"user":{"id":"69d9e1c2-801e-4901-a665-fad467550fec"},"bcat":[],"source":{"ext":{"stype":"prebid_uncn","bidder":"unicorn","prebid_version":"1.0"}}}' + data: '{"id":"5ebea288-f13a-4754-be6d-4ade66c68877","at":1,"imp":[{"id":"216255f234b602","banner":{"w":300,"h":250},"format":[{"w":300,"h":250},{"w":336,"h":280}],"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-0"},{"id":"31e2b28ced2475","banner":{"w":"300","h":"250"},"format":[{"w":"300","h":"250"}],"secure":1,"bidfloor":0"tagid":"/19968336/header-bid-tag-1"},{"id":"40a333e047a9bd","banner":{"w":300,"h":250},"format":[{"w":300,"h":250}],"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-2"}],"cur":"JPY","site":{"id":"uni-corn.net","publisher":{"id":12345},"domain":"uni-corn.net","page":"https://uni-corn.net/","ref":"https://uni-corn.net/"},"device":{"language":"ja","ua":"Mozilla/5.0 (Linux; Android 8.0.0; ONEPLUS A5000) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36"},"user":{"id":"69d9e1c2-801e-4901-a665-fad467550fec"},"bcat":[],"source":{"ext":{"stype":"prebid_uncn","bidder":"unicorn","prebid_version":"1.1"}}}' }; const interpretedBids = [ @@ -410,13 +453,17 @@ const interpretedBids = [ cpm: 1.0017, width: 300, height: 250, + meta: { + advertiserDomains: [ + 'test1.co.jp' + ] + }, ad: '
test
', ttl: 1000, creativeId: 'ABCDE', netRevenue: false, currency: 'JPY' - }, - { + }, { requestId: '31e2b28ced2475', cpm: 0.9513, width: 300, @@ -426,8 +473,7 @@ const interpretedBids = [ creativeId: 'abcde', netRevenue: false, currency: 'JPY' - }, - { + }, { requestId: '40a333e047a9bd', cpm: 0.674, width: 300, diff --git a/test/spec/modules/unrulyBidAdapter_spec.js b/test/spec/modules/unrulyBidAdapter_spec.js index b087ba042a9..6d1d8f9949f 100644 --- a/test/spec/modules/unrulyBidAdapter_spec.js +++ b/test/spec/modules/unrulyBidAdapter_spec.js @@ -1,16 +1,15 @@ /* globals describe, it, beforeEach, afterEach, sinon */ -import { expect } from 'chai' +import {expect} from 'chai' import * as utils from 'src/utils.js' -import { STATUS } from 'src/constants.json' -import { VIDEO } from 'src/mediaTypes.js' -import { Renderer } from 'src/Renderer.js' -import { adapter } from 'modules/unrulyBidAdapter.js' +import {VIDEO, BANNER} from 'src/mediaTypes.js' +import {Renderer} from 'src/Renderer.js' +import {adapter} from 'modules/unrulyBidAdapter.js' describe('UnrulyAdapter', function () { function createOutStreamExchangeBid({ adUnitCode = 'placement2', statusCode = 1, - bidId = 'foo', + requestId = 'foo', vastUrl = 'https://targeting.unrulymedia.com/in_article?uuid=74544e00-d43b-4f3a-a799-69d22ce979ce&supported_mime_type=application/javascript&supported_mime_type=video/mp4&tj=%7B%22site%22%3A%7B%22lang%22%3A%22en-GB%22%2C%22ref%22%3A%22%22%2C%22page%22%3A%22https%3A%2F%2Fdemo.unrulymedia.com%2FinArticle%2Finarticle_nypost_upbeat%2Ftravel_magazines.html%22%2C%22domain%22%3A%22demo.unrulymedia.com%22%7D%2C%22user%22%3A%7B%22profile%22%3A%7B%22quantcast%22%3A%7B%22segments%22%3A%5B%7B%22id%22%3A%22D%22%7D%2C%7B%22id%22%3A%22T%22%7D%5D%7D%7D%7D%7D&video_width=618&video_height=347' }) { return { @@ -30,15 +29,70 @@ describe('UnrulyAdapter', function () { 'bidderCode': 'unruly', 'width': 323, 'vastUrl': vastUrl, - 'bidId': bidId, - 'height': 323 + 'requestId': requestId, + 'creativeId': requestId, + 'height': 323, + 'netRevenue': true, + 'ttl': 360, + 'currency': 'USD', + 'meta': { + 'mediaType': 'video', + 'videoContext': 'outstream' + } } } const createExchangeResponse = (...bids) => ({ - body: { bids } + body: {bids} }); + const inStreamServerResponse = { + 'requestId': '262594d5d1f8104', + 'cpm': 0.3825, + 'currency': 'USD', + 'width': 640, + 'height': 480, + 'creativeId': 'cr-test-video-3', + 'netRevenue': true, + 'ttl': 350, + 'vastUrl': 'https://adserve.rhythmxchange.dvl/rtbtest/nurlvast?event=impnurl&doc_type=testad&doc_version=2&crid=cr-test-video-3&ssp=2057&pubid=545454&placementid=1052819&oppid=b516bc57-0475-4377-bdc6-369c44b31d46&mediatype=site&attempt_ts=1622740567081&extra=1', + 'meta': { + 'mediaType': 'video', + 'videoContext': 'instream' + } + }; + + const inStreamServerResponseWithVastXml = { + 'requestId': '262594d5d1f8104', + 'cpm': 0.3825, + 'currency': 'USD', + 'width': 640, + 'height': 480, + 'creativeId': 'cr-test-video-3', + 'netRevenue': true, + 'ttl': 350, + 'vastXml': 'https://adserve.rhythmxchange.dvl/rtbtest/nurlvast?event=impnurl&doc_type=testad&doc_version=2&crid=cr-test-video-3&ssp=2057&pubid=545454&placementid=1052819&oppid=b516bc57-0475-4377-bdc6-369c44b31d46&mediatype=site&attempt_ts=1622740567081&extra=1', + 'meta': { + 'mediaType': 'video', + 'videoContext': 'instream' + } + }; + + const bannerServerResponse = { + 'requestId': '2de3a9047fa9c6', + 'cpm': 5.34, + 'currency': 'USD', + 'width': 300, + 'height': 250, + 'creativeId': 'cr-test-banner-1', + 'netRevenue': true, + 'ttl': 350, + 'ad': "", + 'meta': { + 'mediaType': 'banner' + } + }; + let sandbox; let fakeRenderer; @@ -63,65 +117,547 @@ describe('UnrulyAdapter', function () { }); it('should contain the VIDEO mediaType', function () { - expect(adapter.supportedMediaTypes).to.deep.equal([ VIDEO ]) + expect(adapter.supportedMediaTypes).to.deep.equal([VIDEO, BANNER]) }); describe('isBidRequestValid', function () { it('should be a function', function () { expect(typeof adapter.isBidRequestValid).to.equal('function') }); - - it('should return false if bid is falsey', function () { + it('should return false if bid is false', function () { expect(adapter.isBidRequestValid()).to.be.false; }); - - it('should return true if bid.mediaType is "video"', function () { - const mockBid = { mediaType: 'video' }; - + it('should return true if bid.mediaType is "banner"', function () { + const mockBid = { + mediaTypes: { + banner: { + sizes: [ + [600, 500], + [300, 250] + ] + } + }, + params: { + siteId: 233261 + } + }; expect(adapter.isBidRequestValid(mockBid)).to.be.true; }); - it('should return true if bid.mediaTypes.video.context is "outstream"', function () { const mockBid = { mediaTypes: { video: { - context: 'outstream' + context: 'outstream', + mimes: ['video/mp4'], + playerSize: [[640, 480]] } + }, + params: { + siteId: 233261 + } + }; + expect(adapter.isBidRequestValid(mockBid)).to.be.true; + }); + it('should return true if bid.mediaTypes.video.context is "instream"', function () { + const mockBid = { + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mp4'], + playerSize: [[640, 480]] + } + }, + params: { + siteId: 233261 } }; - expect(adapter.isBidRequestValid(mockBid)).to.be.true; }); + it('should return false if bid.mediaTypes.video.context is not "instream" or "outstream"', function () { + const mockBid = { + mediaTypes: { + video: { + context: 'context', + mimes: ['video/mp4'], + playerSize: [[640, 480]] + } + }, + params: { + siteId: 233261 + } + }; + expect(adapter.isBidRequestValid(mockBid)).to.be.false; + }); + it('should return false if bid.mediaTypes.video.context not exist', function () { + const mockBid = { + mediaTypes: { + video: { + mimes: ['video/mp4'], + playerSize: [[640, 480]] + } + }, + params: { + siteId: 233261 + } + }; + expect(adapter.isBidRequestValid(mockBid)).to.be.false; + }); + it('should return false if bid.mediaType is not "video" or "banner"', function () { + const mockBid = { + mediaTypes: { + native: { + image: { + sizes: [300, 250] + } + } + }, + params: { + siteId: 233261 + } + }; + expect(adapter.isBidRequestValid(mockBid)).to.be.false; + }); + it('should return false if bid.mediaTypes is empty', function () { + const mockBid = { + mediaTypes: {}, + params: { + siteId: 233261 + } + }; + expect(adapter.isBidRequestValid(mockBid)).to.be.false; + }); + it('should return false if bid.params.siteId not exist', function () { + const mockBid = { + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'], + playerSize: [[640, 480]] + } + }, + params: { + targetingUUID: 233261 + } + }; + expect(adapter.isBidRequestValid(mockBid)).to.be.false; + }); }); describe('buildRequests', function () { + let mockBidRequests; + beforeEach(function () { + mockBidRequests = { + 'bidderCode': 'unruly', + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': [ + 'video/mp4' + ], + 'playerSize': [ + [ + 640, + 480 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + } + ] + }; + }); + it('should be a function', function () { expect(typeof adapter.buildRequests).to.equal('function'); }); it('should return an object', function () { - const mockBidRequests = ['mockBid']; - expect(typeof adapter.buildRequests(mockBidRequests)).to.equal('object') + // const mockBidRequests = ['mockBid']; + expect(typeof adapter.buildRequests(mockBidRequests.bids, mockBidRequests)).to.equal('object') + }); + it('should return an array with 2 items when the bids has different siteId\'s', function () { + mockBidRequests = { + 'bidderCode': 'unruly', + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': [ + 'video/mp4' + ], + 'playerSize': [ + [ + 640, + 480 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + }, + { + 'bidder': 'unruly', + 'params': { + 'siteId': 2234554, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': [ + 'video/mp4' + ], + 'playerSize': [ + [ + 640, + 480 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + } + ] + }; + + let result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); + expect(typeof result).to.equal('object'); + expect(result.length).to.equal(2); + expect(result[0].data.bidderRequest.bids.length).to.equal(1); + expect(result[1].data.bidderRequest.bids.length).to.equal(1); + }); + it('should return an array with 1 items when the bids has same siteId', function () { + mockBidRequests = { + 'bidderCode': 'unruly', + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': [ + 'video/mp4' + ], + 'playerSize': [ + [ + 640, + 480 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + }, + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': [ + 'video/mp4' + ], + 'playerSize': [ + [ + 640, + 480 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + } + ] + }; + + let result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); + expect(typeof result).to.equal('object'); + expect(result.length).to.equal(1); + expect(result[0].data.bidderRequest.bids.length).to.equal(2); }); it('should return a server request with a valid exchange url', function () { - const mockBidRequests = ['mockBid']; - expect(adapter.buildRequests(mockBidRequests).url).to.equal('https://targeting.unrulymedia.com/prebid') + expect(adapter.buildRequests(mockBidRequests.bids, mockBidRequests)[0].url).to.equal('https://targeting.unrulymedia.com/unruly_prebid') + }); + it('should return a server request with a the end point url instead of the exchange url', function () { + mockBidRequests.bids[0].params.endpoint = '//testendpoint.com'; + expect(adapter.buildRequests(mockBidRequests.bids, mockBidRequests)[0].url).to.equal('//testendpoint.com'); }); it('should return a server request with method === POST', function () { - const mockBidRequests = ['mockBid']; - expect(adapter.buildRequests(mockBidRequests).method).to.equal('POST'); + expect(adapter.buildRequests(mockBidRequests.bids, mockBidRequests)[0].method).to.equal('POST'); }); - it('should ensure contentType is `text/plain`', function () { - const mockBidRequests = ['mockBid']; - expect(adapter.buildRequests(mockBidRequests).options).to.deep.equal({ - contentType: 'text/plain' + it('should ensure contentType is `application/json`', function () { + expect(adapter.buildRequests(mockBidRequests.bids, mockBidRequests)[0].options).to.deep.equal({ + contentType: 'application/json' }); }); it('should return a server request with valid payload', function () { - const mockBidRequests = ['mockBid']; - const mockBidderRequest = {bidderCode: 'mockBidder'}; - expect(adapter.buildRequests(mockBidRequests, mockBidderRequest).data) - .to.deep.equal({bidRequests: mockBidRequests, bidderRequest: mockBidderRequest}) - }) + const expectedResult = { + bidderRequest: { + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261 + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': [ + 'video/mp4' + ], + 'playerSize': [ + [ + 640, + 480 + ] + ], + 'floor': 0 + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b' + } + ], + 'invalidBidsCount': 0 + } + }; + + expect(adapter.buildRequests(mockBidRequests.bids, mockBidRequests)[0].data).to.deep.equal(expectedResult) + }); + it('should return request and remove the duplicate sizes', function () { + mockBidRequests = { + 'bidderCode': 'unruly', + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261, + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ], + [ + 640, + 480 + ], + [ + 300, + 250 + ], + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + } + ] + }; + + const expectedResult = { + bidderRequest: { + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ], + [ + 300, + 250 + ] + ], + 'floor': 0 + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + } + ], + 'invalidBidsCount': 0 + } + }; + + let result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); + expect(result[0].data).to.deep.equal(expectedResult); + }); + + it('should return have the floor value from the bid', function () { + mockBidRequests = { + 'bidderCode': 'unruly', + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261, + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ] + ] + } + }, + 'floors': { + 'enforceFloors': true, + 'currency': 'USD', + 'schema': { + 'fields': [ + 'mediaType' + ] + }, + 'values': { + 'banner': 3 + }, + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + } + ] + }; + + const getFloor = (data) => { + return {floor: 3} + }; + + mockBidRequests.bids[0].getFloor = getFloor; + + const expectedResult = { + bidderRequest: { + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ] + ], + 'floor': 3 + } + }, + 'floors': { + 'enforceFloors': true, + 'currency': 'USD', + 'schema': { + 'fields': [ + 'mediaType' + ] + }, + 'values': { + 'banner': 3 + }, + }, + getFloor: getFloor, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + } + ], + 'invalidBidsCount': 0 + } + }; + + let result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); + expect(result[0].data).to.deep.equal(expectedResult); + }); }); describe('interpretResponse', function () { @@ -132,15 +668,28 @@ describe('UnrulyAdapter', function () { expect(adapter.interpretResponse()).to.deep.equal([]); }); it('should return [] when serverResponse has no bids', function () { - const mockServerResponse = { body: { bids: [] } }; + const mockServerResponse = {body: {bids: []}}; expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([]) }); it('should return array of bids when receive a successful response from server', function () { - const mockExchangeBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); + const mockExchangeBid = createOutStreamExchangeBid({adUnitCode: 'video1', requestId: 'mockBidId'}); const mockServerResponse = createExchangeResponse(mockExchangeBid); expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([ { + 'ext': { + 'statusCode': 1, + 'renderer': { + 'id': 'unruly_inarticle', + 'config': { + 'siteId': 123456, + 'targetingUUID': 'xxx-yyy-zzz' + }, + 'url': 'https://video.unrulymedia.com/native/prebid-loader.js' + }, + 'adUnitCode': 'video1' + }, requestId: 'mockBidId', + bidderCode: 'unruly', cpm: 20, width: 323, height: 323, @@ -148,7 +697,10 @@ describe('UnrulyAdapter', function () { netRevenue: true, creativeId: 'mockBidId', ttl: 360, - meta: { advertiserDomains: [] }, + 'meta': { + 'mediaType': 'video', + 'videoContext': 'outstream' + }, currency: 'USD', renderer: fakeRenderer, mediaType: 'video' @@ -160,7 +712,7 @@ describe('UnrulyAdapter', function () { expect(Renderer.install.called).to.be.false; expect(fakeRenderer.setRender.called).to.be.false; - const mockReturnedBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); + const mockReturnedBid = createOutStreamExchangeBid({adUnitCode: 'video1', requestId: 'mockBidId'}); const mockRenderer = { url: 'value: mockRendererURL', config: { @@ -176,7 +728,7 @@ describe('UnrulyAdapter', function () { expect(Renderer.install.calledOnce).to.be.true; sinon.assert.calledWithExactly( Renderer.install, - Object.assign({}, mockRenderer, {callback: sinon.match.func}) + Object.assign({}, mockRenderer) ); sinon.assert.calledOnce(fakeRenderer.setRender); @@ -184,12 +736,12 @@ describe('UnrulyAdapter', function () { }); it('should return [] and log if bidResponse renderer config is not available', function () { - sinon.assert.notCalled(utils.logError) + sinon.assert.notCalled(utils.logError); expect(Renderer.install.called).to.be.false; expect(fakeRenderer.setRender.called).to.be.false; - const mockReturnedBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); + const mockReturnedBid = createOutStreamExchangeBid({adUnitCode: 'video1', requestId: 'mockBidId'}); const mockRenderer = { url: 'value: mockRendererURL' }; @@ -199,24 +751,21 @@ describe('UnrulyAdapter', function () { expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([]); const logErrorCalls = utils.logError.getCalls(); - expect(logErrorCalls.length).to.equal(2); + expect(logErrorCalls.length).to.equal(1); - const [ configErrorCall, siteIdErrorCall ] = logErrorCalls; + const [configErrorCall] = logErrorCalls; expect(configErrorCall.args.length).to.equal(1); expect(configErrorCall.args[0].message).to.equal('UnrulyBidAdapter: Missing renderer config.'); - - expect(siteIdErrorCall.args.length).to.equal(1); - expect(siteIdErrorCall.args[0].message).to.equal('UnrulyBidAdapter: Missing renderer siteId.'); }); it('should return [] and log if siteId is not available', function () { - sinon.assert.notCalled(utils.logError) + sinon.assert.notCalled(utils.logError); expect(Renderer.install.called).to.be.false; expect(fakeRenderer.setRender.called).to.be.false; - const mockReturnedBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); + const mockReturnedBid = createOutStreamExchangeBid({adUnitCode: 'video1', requestId: 'mockBidId'}); const mockRenderer = { url: 'value: mockRendererURL', config: {} @@ -229,14 +778,14 @@ describe('UnrulyAdapter', function () { const logErrorCalls = utils.logError.getCalls(); expect(logErrorCalls.length).to.equal(1); - const [ siteIdErrorCall ] = logErrorCalls; + const [siteIdErrorCall] = logErrorCalls; expect(siteIdErrorCall.args.length).to.equal(1); expect(siteIdErrorCall.args[0].message).to.equal('UnrulyBidAdapter: Missing renderer siteId.'); }); it('bid is placed on the bid queue when render is called', function () { - const exchangeBid = createOutStreamExchangeBid({ adUnitCode: 'video', vastUrl: 'value: vastUrl' }); + const exchangeBid = createOutStreamExchangeBid({adUnitCode: 'video', vastUrl: 'value: vastUrl'}); const exchangeResponse = createExchangeResponse(exchangeBid); adapter.interpretResponse(exchangeResponse); @@ -256,7 +805,7 @@ describe('UnrulyAdapter', function () { }); it('should ensure that renderer is placed in Prebid supply mode', function () { - const mockExchangeBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); + const mockExchangeBid = createOutStreamExchangeBid({adUnitCode: 'video1', requestId: 'mockBidId'}); const mockServerResponse = createExchangeResponse(mockExchangeBid); expect('unruly' in window.parent).to.equal(false); @@ -267,65 +816,94 @@ describe('UnrulyAdapter', function () { expect(supplyMode).to.equal('prebid'); }); - }); - describe('getUserSyncs', () => { - it('should push user sync iframe if enabled', () => { - const mockConsent = {} - const response = {} - const syncOptions = { iframeEnabled: true } - const syncs = adapter.getUserSyncs(syncOptions, response, mockConsent) - expect(syncs[0]).to.deep.equal({ - type: 'iframe', - url: 'https://video.unrulymedia.com/iframes/third-party-iframes.html' - }); - }) + it('should return correct response when ad type is instream with vastUrl', function () { + const mockServerResponse = createExchangeResponse(inStreamServerResponse); + const expectedResponse = inStreamServerResponse; + expectedResponse.mediaType = 'video'; - it('should not push user sync iframe if not enabled', () => { - const mockConsent = {} - const response = {} - const syncOptions = { iframeEnabled: false } - const syncs = adapter.getUserSyncs(syncOptions, response, mockConsent); - expect(syncs).to.be.empty; + expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([expectedResponse]); }); - }); - it('should not append consent params if gdpr does not apply', () => { - const mockConsent = {} - const response = {} - const syncOptions = { iframeEnabled: true } - const syncs = adapter.getUserSyncs(syncOptions, response, mockConsent) - expect(syncs[0]).to.deep.equal({ - type: 'iframe', - url: 'https://video.unrulymedia.com/iframes/third-party-iframes.html' - }) - }); + it('should return correct response when ad type is instream with vastXml', function () { + const mockServerResponse = {...createExchangeResponse(inStreamServerResponseWithVastXml)}; + const expectedResponse = inStreamServerResponseWithVastXml; + expectedResponse.mediaType = 'video'; - it('should append consent params if gdpr does apply and consent is given', () => { - const mockConsent = { - gdprApplies: true, - consentString: 'hello' - }; - const response = {} - const syncOptions = { iframeEnabled: true } - const syncs = adapter.getUserSyncs(syncOptions, response, mockConsent) - expect(syncs[0]).to.deep.equal({ - type: 'iframe', - url: 'https://video.unrulymedia.com/iframes/third-party-iframes.html?gdpr=1&gdpr_consent=hello' - }) - }); + expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([expectedResponse]); + }); - it('should append consent param if gdpr applies and no consent is given', () => { - const mockConsent = { - gdprApplies: true, - consentString: {} - }; - const response = {}; - const syncOptions = { iframeEnabled: true } - const syncs = adapter.getUserSyncs(syncOptions, response, mockConsent) - expect(syncs[0]).to.deep.equal({ - type: 'iframe', - url: 'https://video.unrulymedia.com/iframes/third-party-iframes.html?gdpr=0' - }) - }) + it('should return [] and log if no vastUrl in instream response', function () { + const {vastUrl, ...inStreamServerResponseNoVast} = inStreamServerResponse; + const mockServerResponse = createExchangeResponse(inStreamServerResponseNoVast); + + expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([]); + + const logErrorCalls = utils.logError.getCalls(); + + expect(logErrorCalls.length).to.equal(1); + + const [siteIdErrorCall] = logErrorCalls; + + expect(siteIdErrorCall.args.length).to.equal(1); + expect(siteIdErrorCall.args[0].message).to.equal('UnrulyBidAdapter: Missing vastUrl or vastXml config.'); + }); + + it('should return correct response when ad type is banner', function () { + const mockServerResponse = createExchangeResponse(bannerServerResponse); + const expectedResponse = bannerServerResponse; + expectedResponse.mediaType = 'banner'; + + expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([expectedResponse]); + }); + + it('should return [] and log if no ad in banner response', function () { + const {ad, ...bannerServerResponseNoAd} = bannerServerResponse; + const mockServerResponse = createExchangeResponse(bannerServerResponseNoAd); + + expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([]); + + const logErrorCalls = utils.logError.getCalls(); + + expect(logErrorCalls.length).to.equal(1); + + const [siteIdErrorCall] = logErrorCalls; + + expect(siteIdErrorCall.args.length).to.equal(1); + expect(siteIdErrorCall.args[0].message).to.equal('UnrulyBidAdapter: Missing ad config.'); + }); + + it('should return correct response for multiple bids', function () { + const outStreamServerResponse = createOutStreamExchangeBid({adUnitCode: 'video1', requestId: 'mockBidId'}); + const mockServerResponse = createExchangeResponse(outStreamServerResponse, inStreamServerResponse, bannerServerResponse); + const expectedOutStreamResponse = outStreamServerResponse; + expectedOutStreamResponse.mediaType = 'video'; + + const expectedInStreamResponse = inStreamServerResponse; + expectedInStreamResponse.mediaType = 'video'; + + const expectedBannerResponse = bannerServerResponse; + expectedBannerResponse.mediaType = 'banner'; + + expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([expectedOutStreamResponse, expectedInStreamResponse, expectedBannerResponse]); + }); + + it('should return only valid bids', function () { + const {ad, ...bannerServerResponseNoAd} = bannerServerResponse; + const mockServerResponse = createExchangeResponse(bannerServerResponseNoAd, inStreamServerResponse); + const expectedInStreamResponse = inStreamServerResponse; + expectedInStreamResponse.mediaType = 'video'; + + expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([expectedInStreamResponse]); + + const logErrorCalls = utils.logError.getCalls(); + + expect(logErrorCalls.length).to.equal(1); + + const [siteIdErrorCall] = logErrorCalls; + + expect(siteIdErrorCall.args.length).to.equal(1); + expect(siteIdErrorCall.args[0].message).to.equal('UnrulyBidAdapter: Missing ad config.'); + }); + }); }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 95bba079fa2..1897f23de03 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -25,7 +25,6 @@ import { import {server} from 'test/mocks/xhr.js'; import find from 'core-js-pure/features/array/find.js'; import {unifiedIdSubmodule} from 'modules/unifiedIdSystem.js'; -import {pubCommonIdSubmodule} from 'modules/pubCommonIdSystem.js'; import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; import {id5IdSubmodule} from 'modules/id5IdSystem.js'; import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; @@ -36,8 +35,8 @@ import {netIdSubmodule} from 'modules/netIdSystem.js'; import {nextrollIdSubmodule} from 'modules/nextrollIdSystem.js'; import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; import {zeotapIdPlusSubmodule} from 'modules/zeotapIdPlusIdSystem.js'; -import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; -import {haloIdSubmodule} from 'modules/haloIdSystem.js'; +import {sharedIdSystemSubmodule} from 'modules/sharedIdSystem.js'; +import {hadronIdSubmodule} from 'modules/hadronIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; import {mwOpenLinkIdSubModule} from 'modules/mwOpenLinkIdSystem.js'; @@ -47,6 +46,11 @@ import {uid2IdSubmodule} from 'modules/uid2IdSystem.js'; import {admixerIdSubmodule} from 'modules/admixerIdSystem.js'; import {deepintentDpesSubmodule} from 'modules/deepintentDpesIdSystem.js'; import {flocIdSubmodule} from 'modules/flocIdSystem.js' +import {amxIdSubmodule} from '../../../modules/amxIdSystem.js'; +import {akamaiDAPIdSubmodule} from 'modules/akamaiDAPIdSystem.js' +import {kinessoIdSubmodule} from 'modules/kinessoIdSystem.js' +import {adqueryIdSubmodule} from 'modules/adqueryIdSystem.js'; +import * as mockGpt from '../integration/faker/googletag.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -112,15 +116,21 @@ describe('User ID', function () { describe('Decorate Ad Units', function () { beforeEach(function () { + // reset mockGpt so nothing else interferes + mockGpt.disable(); + mockGpt.enable(); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('pubcid_alt', 'altpubcid200000', (new Date(Date.now() + 5000).toUTCString())); sinon.spy(coreStorage, 'setCookie'); + sinon.stub(utils, 'logWarn'); }); afterEach(function () { + mockGpt.enable(); $$PREBID_GLOBAL$$.requestBids.removeAll(); config.resetConfig(); coreStorage.setCookie.restore(); + utils.logWarn.restore(); }); after(function () { @@ -137,7 +147,7 @@ describe('User ID', function () { let pubcid = coreStorage.getCookie('pubcid'); expect(pubcid).to.be.null; // there should be no cookie initially - setSubmoduleRegistry([pubCommonIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); @@ -171,7 +181,7 @@ describe('User ID', function () { let pubcid1; let pubcid2; - setSubmoduleRegistry([pubCommonIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); requestBidsHook((config) => { @@ -191,7 +201,7 @@ describe('User ID', function () { }); }); - setSubmoduleRegistry([pubCommonIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); requestBidsHook((config) => { @@ -218,7 +228,7 @@ describe('User ID', function () { let adUnits = [getAdUnitMock()]; let innerAdUnits; - setSubmoduleRegistry([pubCommonIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie'])); requestBidsHook((config) => { @@ -234,9 +244,10 @@ describe('User ID', function () { }); }); }); - // Because the cookie exists already, there should be no setCookie call by default; the only setCookie call is - // to store consent data - expect(coreStorage.setCookie.callCount).to.equal(1); + // Because the consent cookie doesn't exist yet, we'll have two setCookie calls: + // 1) for the consent cookie + // 2) from the getId() call that results in a new call to store the results + expect(coreStorage.setCookie.callCount).to.equal(2); }); it('Extend cookie', function () { @@ -245,7 +256,7 @@ describe('User ID', function () { let customConfig = getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie']); customConfig = addConfig(customConfig, 'params', {extend: true}); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); init(config); config.setConfig(customConfig); requestBidsHook((config) => { @@ -272,7 +283,7 @@ describe('User ID', function () { let customConfig = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); customConfig = addConfig(customConfig, 'params', {create: false}); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); init(config); config.setConfig(customConfig); requestBidsHook((config) => { @@ -289,7 +300,7 @@ describe('User ID', function () { }); it('pbjs.getUserIds', function () { - setSubmoduleRegistry([pubCommonIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig({ userSync: { @@ -304,7 +315,7 @@ describe('User ID', function () { }); it('pbjs.getUserIdsAsEids', function () { - setSubmoduleRegistry([pubCommonIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig({ userSync: { @@ -318,6 +329,50 @@ describe('User ID', function () { expect((getGlobal()).getUserIdsAsEids()).to.deep.equal(createEidsArray((getGlobal()).getUserIds())); }); + it('should set googletag ppid correctly', function () { + let adUnits = [getAdUnitMock()]; + setSubmoduleRegistry([amxIdSubmodule, sharedIdSystemSubmodule, identityLinkSubmodule]); + init(config); + + config.setConfig({ + userSync: { + ppid: 'pubcid.org', + userIds: [ + { name: 'amxId', value: {'amxId': 'amx-id-value-amx-id-value-amx-id-value'} }, + { name: 'pubCommonId', value: {'pubcid': 'pubCommon-id-value-pubCommon-id-value'} }, + { name: 'identityLink', value: {'idl_env': 'identityLink-id-value-identityLink-id-value'} }, + ] + } + }); + // before ppid should not be set + expect(window.googletag._ppid).to.equal(undefined); + requestBidsHook(() => {}, {adUnits}); + // ppid should have been set without dashes and stuff + expect(window.googletag._ppid).to.equal('pubCommonidvaluepubCommonidvalue'); + }); + + it('should log a warning if PPID too big or small', function () { + let adUnits = [getAdUnitMock()]; + setSubmoduleRegistry([sharedIdSystemSubmodule]); + init(config); + + config.setConfig({ + userSync: { + ppid: 'pubcid.org', + userIds: [ + { name: 'pubCommonId', value: {'pubcid': 'pubcommonIdValue'} }, + ] + } + }); + // before ppid should not be set + expect(window.googletag._ppid).to.equal(undefined); + requestBidsHook(() => {}, {adUnits}); + // ppid should NOT have been set + expect(window.googletag._ppid).to.equal(undefined); + // a warning should have been emmited + expect(utils.logWarn.args[0][0]).to.exist.and.to.contain('User ID - Googletag Publisher Provided ID for pubcid.org is not between 32 and 150 characters - pubcommonIdValue'); + }); + it('pbjs.refreshUserIds refreshes', function() { let sandbox = sinon.createSandbox(); @@ -363,6 +418,46 @@ describe('User ID', function () { expect(mockIdCallback.callCount).to.equal(1); }); + it('pbjs.refreshUserIds updates submodules', function() { + let sandbox = sinon.createSandbox(); + let mockIdCallback = sandbox.stub().returns({id: {'MOCKID': '1111'}}); + let mockIdSystem = { + name: 'mockId', + decode: function(value) { + return { + 'mid': value['MOCKID'] + }; + }, + getId: mockIdCallback + }; + setSubmoduleRegistry([mockIdSystem]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [{ + name: 'mockId', + value: {id: {mockId: '1111'}} + }] + } + }); + + expect(getGlobal().getUserIds().id.mockId).to.equal('1111'); + + // update to new config value + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [{ + name: 'mockId', + value: {id: {mockId: '1212'}} + }] + } + }); + getGlobal().refreshUserIds({ submoduleNames: ['mockId'] }); + expect(getGlobal().getUserIds().id.mockId).to.equal('1212'); + }); + it('pbjs.refreshUserIds refreshes single', function() { coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('REFRESH', '', EXPIRED_COOKIE_DATE); @@ -439,14 +534,14 @@ describe('User ID', function () { }); it('fails initialization if opt out cookie exists', function () { - setSubmoduleRegistry([pubCommonIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - opt-out cookie found, exit module'); }); it('initializes if no opt out cookie exists', function () { - setSubmoduleRegistry([pubCommonIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); @@ -465,7 +560,7 @@ describe('User ID', function () { }); it('handles config with no usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -473,14 +568,14 @@ describe('User ID', function () { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -491,7 +586,7 @@ describe('User ID', function () { }); it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -508,7 +603,7 @@ describe('User ID', function () { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); @@ -529,8 +624,8 @@ describe('User ID', function () { expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 21 configurations should result in 21 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule]); + it('config with 24 configurations should result in 24 submodules add', function () { + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -559,15 +654,12 @@ describe('User ID', function () { storage: {name: 'netId', type: 'cookie'} }, { name: 'nextrollId' - }, { - name: 'sharedId', - storage: {name: 'sharedid', type: 'cookie'} }, { name: 'intentIqId', storage: {name: 'intentIqId', type: 'cookie'} }, { - name: 'haloId', - storage: {name: 'haloId', type: 'cookie'} + name: 'hadronId', + storage: {name: 'hadronId', type: 'cookie'} }, { name: 'zeotapIdPlus' }, { @@ -587,17 +679,28 @@ describe('User ID', function () { storage: {name: 'deepintentId', type: 'cookie'} }, { name: 'flocId' + }, { + name: 'akamaiDAPId' }, { name: 'dmdId', storage: {name: 'dmdId', type: 'cookie'} + }, { + name: 'amxId', + storage: {name: 'amxId', type: 'html5'} + }, { + name: 'kpuid', + storage: {name: 'kpuid', type: 'cookie'} + }, { + name: 'qid', + storage: {name: 'qid', type: 'html5'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 21 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 24 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ @@ -613,7 +716,7 @@ describe('User ID', function () { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -628,7 +731,7 @@ describe('User ID', function () { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -641,738 +744,701 @@ describe('User ID', function () { }); expect(auctionDelay).to.equal(0); }); - }); - describe('auction and user sync delays', function () { - let sandbox; - let adUnits; - let mockIdCallback; - let auctionSpy; + describe('auction and user sync delays', function () { + let sandbox; + let adUnits; + let mockIdCallback; + let auctionSpy; - beforeEach(function () { - sandbox = sinon.createSandbox(); - sandbox.stub(global, 'setTimeout').returns(2); - sandbox.stub(global, 'clearTimeout'); - sandbox.stub(events, 'on'); - sandbox.stub(coreStorage, 'getCookie'); + beforeEach(function () { + sandbox = sinon.createSandbox(); + sandbox.stub(global, 'setTimeout').returns(2); + sandbox.stub(global, 'clearTimeout'); + sandbox.stub(events, 'on'); + sandbox.stub(coreStorage, 'getCookie'); - // remove cookie - coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); + // remove cookie + coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); - adUnits = [getAdUnitMock()]; + adUnits = [getAdUnitMock()]; - auctionSpy = sandbox.spy(); - mockIdCallback = sandbox.stub(); - const mockIdSystem = { - name: 'mockId', - decode: function (value) { - return { - 'mid': value['MOCKID'] - }; - }, - getId: function () { - const storedId = coreStorage.getCookie('MOCKID'); - if (storedId) { - return {id: {'MOCKID': storedId}}; + auctionSpy = sandbox.spy(); + mockIdCallback = sandbox.stub(); + const mockIdSystem = { + name: 'mockId', + decode: function (value) { + return { + 'mid': value['MOCKID'] + }; + }, + getId: function () { + const storedId = coreStorage.getCookie('MOCKID'); + if (storedId) { + return {id: {'MOCKID': storedId}}; + } + return {callback: mockIdCallback}; } - return {callback: mockIdCallback}; - } - }; - - init(config); + }; - attachIdSystem(mockIdSystem, true); - }); + init(config); - afterEach(function () { - $$PREBID_GLOBAL$$.requestBids.removeAll(); - config.resetConfig(); - sandbox.restore(); - }); + attachIdSystem(mockIdSystem, true); + }); - it('delays auction if auctionDelay is set, timing out at auction delay', function () { - config.setConfig({ - userSync: { - auctionDelay: 33, - syncDelay: 77, - userIds: [{ - name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} - }] - } + afterEach(function () { + $$PREBID_GLOBAL$$.requestBids.removeAll(); + config.resetConfig(); + sandbox.restore(); }); - requestBidsHook(auctionSpy, {adUnits}); + it('delays auction if auctionDelay is set, timing out at auction delay', function () { + config.setConfig({ + userSync: { + auctionDelay: 33, + syncDelay: 77, + userIds: [{ + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} + }] + } + }); - // check auction was delayed - global.clearTimeout.calledOnce.should.equal(false); - global.setTimeout.calledOnce.should.equal(true); - global.setTimeout.calledWith(sinon.match.func, 33); - auctionSpy.calledOnce.should.equal(false); + requestBidsHook(auctionSpy, {adUnits}); - // check ids were fetched - mockIdCallback.calledOnce.should.equal(true); + // check auction was delayed + global.clearTimeout.calledOnce.should.equal(false); + global.setTimeout.calledOnce.should.equal(true); + global.setTimeout.calledWith(sinon.match.func, 33); + auctionSpy.calledOnce.should.equal(false); - // callback to continue auction if timed out - global.setTimeout.callArg(0); - auctionSpy.calledOnce.should.equal(true); + // check ids were fetched + mockIdCallback.calledOnce.should.equal(true); - // does not call auction again once ids are synced - mockIdCallback.callArgWith(0, {'MOCKID': '1234'}); - auctionSpy.calledOnce.should.equal(true); + // callback to continue auction if timed out + global.setTimeout.callArg(0); + auctionSpy.calledOnce.should.equal(true); - // no sync after auction ends - events.on.called.should.equal(false); - }); + // does not call auction again once ids are synced + mockIdCallback.callArgWith(0, {'MOCKID': '1234'}); + auctionSpy.calledOnce.should.equal(true); - it('delays auction if auctionDelay is set, continuing auction if ids are fetched before timing out', function (done) { - config.setConfig({ - userSync: { - auctionDelay: 33, - syncDelay: 77, - userIds: [{ - name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} - }] - } + // no sync after auction ends + events.on.called.should.equal(false); }); - requestBidsHook(auctionSpy, {adUnits}); + it('delays auction if auctionDelay is set, continuing auction if ids are fetched before timing out', function (done) { + config.setConfig({ + userSync: { + auctionDelay: 33, + syncDelay: 77, + userIds: [{ + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} + }] + } + }); + + requestBidsHook(auctionSpy, {adUnits}); - // check auction was delayed - // global.setTimeout.calledOnce.should.equal(true); - global.clearTimeout.calledOnce.should.equal(false); - global.setTimeout.calledWith(sinon.match.func, 33); - auctionSpy.calledOnce.should.equal(false); + // check auction was delayed + // global.setTimeout.calledOnce.should.equal(true); + global.clearTimeout.calledOnce.should.equal(false); + global.setTimeout.calledWith(sinon.match.func, 33); + auctionSpy.calledOnce.should.equal(false); - // check ids were fetched - mockIdCallback.calledOnce.should.equal(true); + // check ids were fetched + mockIdCallback.calledOnce.should.equal(true); - // if ids returned, should continue auction - mockIdCallback.callArgWith(0, {'MOCKID': '1234'}); - auctionSpy.calledOnce.should.equal(true); + // if ids returned, should continue auction + mockIdCallback.callArgWith(0, {'MOCKID': '1234'}); + auctionSpy.calledOnce.should.equal(true); - // check ids were copied to bids - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.mid'); - expect(bid.userId.mid).to.equal('1234'); - expect(bid.userIdAsEids.length).to.equal(0);// "mid" is an un-known submodule for USER_IDS_CONFIG in eids.js + // check ids were copied to bids + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.mid'); + expect(bid.userId.mid).to.equal('1234'); + expect(bid.userIdAsEids.length).to.equal(0);// "mid" is an un-known submodule for USER_IDS_CONFIG in eids.js + }); + done(); }); - done(); - }); - - // no sync after auction ends - events.on.called.should.equal(false); - }); - it('does not delay auction if not set, delays id fetch after auction ends with syncDelay', function () { - config.setConfig({ - userSync: { - syncDelay: 77, - userIds: [{ - name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} - }] - } + // no sync after auction ends + events.on.called.should.equal(false); }); - // check config has been set correctly - expect(auctionDelay).to.equal(0); - expect(syncDelay).to.equal(77); + it('does not delay auction if not set, delays id fetch after auction ends with syncDelay', function () { + config.setConfig({ + userSync: { + syncDelay: 77, + userIds: [{ + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} + }] + } + }); - requestBidsHook(auctionSpy, {adUnits}); + // check config has been set correctly + expect(auctionDelay).to.equal(0); + expect(syncDelay).to.equal(77); - // should not delay auction - global.setTimeout.calledOnce.should.equal(false); - auctionSpy.calledOnce.should.equal(true); + requestBidsHook(auctionSpy, {adUnits}); - // check user sync is delayed after auction is ended - mockIdCallback.calledOnce.should.equal(false); - events.on.calledOnce.should.equal(true); - events.on.calledWith(CONSTANTS.EVENTS.AUCTION_END, sinon.match.func); + // should not delay auction + global.setTimeout.calledOnce.should.equal(false); + auctionSpy.calledOnce.should.equal(true); - // once auction is ended, sync user ids after delay - events.on.callArg(1); - global.setTimeout.calledOnce.should.equal(true); - global.setTimeout.calledWith(sinon.match.func, 77); - mockIdCallback.calledOnce.should.equal(false); + // check user sync is delayed after auction is ended + mockIdCallback.calledOnce.should.equal(false); + events.on.calledOnce.should.equal(true); + events.on.calledWith(CONSTANTS.EVENTS.AUCTION_END, sinon.match.func); - // once sync delay is over, ids should be fetched - global.setTimeout.callArg(0); - mockIdCallback.calledOnce.should.equal(true); - }); + // once auction is ended, sync user ids after delay + events.on.callArg(1); + global.setTimeout.calledOnce.should.equal(true); + global.setTimeout.calledWith(sinon.match.func, 77); + mockIdCallback.calledOnce.should.equal(false); - it('does not delay user id sync after auction ends if set to 0', function () { - config.setConfig({ - userSync: { - syncDelay: 0, - userIds: [{ - name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} - }] - } + // once sync delay is over, ids should be fetched + global.setTimeout.callArg(0); + mockIdCallback.calledOnce.should.equal(true); }); - expect(syncDelay).to.equal(0); + it('does not delay user id sync after auction ends if set to 0', function () { + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [{ + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} + }] + } + }); - requestBidsHook(auctionSpy, {adUnits}); + expect(syncDelay).to.equal(0); - // auction should not be delayed - global.setTimeout.calledOnce.should.equal(false); - auctionSpy.calledOnce.should.equal(true); + requestBidsHook(auctionSpy, {adUnits}); - // sync delay after auction is ended - mockIdCallback.calledOnce.should.equal(false); - events.on.calledOnce.should.equal(true); - events.on.calledWith(CONSTANTS.EVENTS.AUCTION_END, sinon.match.func); + // auction should not be delayed + global.setTimeout.calledOnce.should.equal(false); + auctionSpy.calledOnce.should.equal(true); - // once auction is ended, if no sync delay, fetch ids - events.on.callArg(1); - global.setTimeout.calledOnce.should.equal(false); - mockIdCallback.calledOnce.should.equal(true); - }); + // sync delay after auction is ended + mockIdCallback.calledOnce.should.equal(false); + events.on.calledOnce.should.equal(true); + events.on.calledWith(CONSTANTS.EVENTS.AUCTION_END, sinon.match.func); - it('does not delay auction if there are no ids to fetch', function () { - coreStorage.getCookie.withArgs('MOCKID').returns('123456778'); - config.setConfig({ - userSync: { - auctionDelay: 33, - syncDelay: 77, - userIds: [{ - name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} - }] - } + // once auction is ended, if no sync delay, fetch ids + events.on.callArg(1); + global.setTimeout.calledOnce.should.equal(false); + mockIdCallback.calledOnce.should.equal(true); }); - requestBidsHook(auctionSpy, {adUnits}); + it('does not delay auction if there are no ids to fetch', function () { + coreStorage.getCookie.withArgs('MOCKID').returns('123456778'); + config.setConfig({ + userSync: { + auctionDelay: 33, + syncDelay: 77, + userIds: [{ + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} + }] + } + }); + + requestBidsHook(auctionSpy, {adUnits}); - global.setTimeout.calledOnce.should.equal(false); - auctionSpy.calledOnce.should.equal(true); - mockIdCallback.calledOnce.should.equal(false); + global.setTimeout.calledOnce.should.equal(false); + auctionSpy.calledOnce.should.equal(true); + mockIdCallback.calledOnce.should.equal(false); - // no sync after auction ends - events.on.called.should.equal(false); + // no sync after auction ends + events.on.called.should.equal(false); + }); }); - }); - describe('Request bids hook appends userId to bid objs in adapters', function () { - let adUnits; + describe('Request bids hook appends userId to bid objs in adapters', function () { + let adUnits; - beforeEach(function () { - adUnits = [getAdUnitMock()]; - }); + beforeEach(function () { + adUnits = [getAdUnitMock()]; + }); - it('test hook from pubcommonid cookie', function (done) { - coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 100000).toUTCString())); + it('test hook from pubcommonid cookie', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 100000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); + setSubmoduleRegistry([sharedIdSystemSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'pubcid.org', - uids: [{id: 'testpubcid', atype: 1}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'pubcid.org', + uids: [{id: 'testpubcid', atype: 1}] + }); }); - - // verify no sharedid was added - expect(bid.userId).to.not.have.property('sharedid'); - expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.be.undefined; }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from pubcommonid html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('pubcid', 'testpubcid'); - localStorage.setItem('pubcid_exp', new Date(Date.now() + 100000).toUTCString()); + it('test hook from pubcommonid html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('pubcid', 'testpubcid'); + localStorage.setItem('pubcid_exp', new Date(Date.now() + 100000).toUTCString()); - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'html5'])); + setSubmoduleRegistry([sharedIdSystemSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'pubcid.org', - uids: [{id: 'testpubcid', atype: 1}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'pubcid.org', + uids: [{id: 'testpubcid', atype: 1}] + }); }); - - // verify no sharedid was added - expect(bid.userId).to.not.have.property('sharedid'); - expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.be.undefined; }); - }); - localStorage.removeItem('pubcid'); - localStorage.removeItem('pubcid_exp'); - done(); - }, {adUnits}); - }); - - it('test hook from pubcommonid config value object', function (done) { - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - config.setConfig(getConfigValueMock('pubCommonId', {'pubcidvalue': 'testpubcidvalue'})); + localStorage.removeItem('pubcid'); + localStorage.removeItem('pubcid_exp'); + done(); + }, {adUnits}); + }); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.pubcidvalue'); - expect(bid.userId.pubcidvalue).to.equal('testpubcidvalue'); - expect(bid.userIdAsEids.length).to.equal(0);// "pubcidvalue" is an un-known submodule for USER_IDS_CONFIG in eids.js + it('test hook from pubcommonid config value object', function (done) { + setSubmoduleRegistry([sharedIdSystemSubmodule]); + init(config); + config.setConfig(getConfigValueMock('pubCommonId', {'pubcidvalue': 'testpubcidvalue'})); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubcidvalue'); + expect(bid.userId.pubcidvalue).to.equal('testpubcidvalue'); + expect(bid.userIdAsEids.length).to.equal(0);// "pubcidvalue" is an un-known submodule for USER_IDS_CONFIG in eids.js + }); }); - }); - done(); - }, {adUnits}); - }); + done(); + }, {adUnits}); + }); - it('test hook from UnifiedId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('unifiedid_alt', JSON.stringify({'TDID': 'testunifiedid_alt'})); - localStorage.setItem('unifiedid_alt_exp', ''); + it('test hook from UnifiedId html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('unifiedid_alt', JSON.stringify({'TDID': 'testunifiedid_alt'})); + localStorage.setItem('unifiedid_alt_exp', ''); - setSubmoduleRegistry([unifiedIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['unifiedId', 'unifiedid_alt', 'html5'])); + setSubmoduleRegistry([unifiedIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['unifiedId', 'unifiedid_alt', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.tdid'); - expect(bid.userId.tdid).to.equal('testunifiedid_alt'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'adserver.org', - uids: [{id: 'testunifiedid_alt', atype: 1, ext: {rtiPartner: 'TDID'}}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.tdid'); + expect(bid.userId.tdid).to.equal('testunifiedid_alt'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'adserver.org', + uids: [{id: 'testunifiedid_alt', atype: 1, ext: {rtiPartner: 'TDID'}}] + }); }); }); - }); - localStorage.removeItem('unifiedid_alt'); - localStorage.removeItem('unifiedid_alt_exp'); - done(); - }, {adUnits}); - }); + localStorage.removeItem('unifiedid_alt'); + localStorage.removeItem('unifiedid_alt_exp'); + done(); + }, {adUnits}); + }); - it('test hook from identityLink html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('idl_env', 'AiGNC8Z5ONyZKSpIPf'); - localStorage.setItem('idl_env_exp', ''); + it('test hook from amxId html5', (done) => { + // simulate existing localStorage values + localStorage.setItem('amxId', 'test_amxid_id'); + localStorage.setItem('amxId_exp', ''); - setSubmoduleRegistry([identityLinkSubmodule]); - init(config); - config.setConfig(getConfigMock(['identityLink', 'idl_env', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.idl_env'); - expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveramp.com', - uids: [{id: 'AiGNC8Z5ONyZKSpIPf', atype: 3}] + setSubmoduleRegistry([amxIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['amxId', 'amxId', 'html5'])); + + requestBidsHook(() => { + adUnits.forEach((adUnit) => { + adUnit.bids.forEach((bid) => { + expect(bid).to.have.deep.nested.property('userId.amxId'); + expect(bid.userId.amxId).to.equal('test_amxid_id'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'amxrtb.com', + uids: [{ + id: 'test_amxid_id', + atype: 1, + }] + }); }); }); - }); - localStorage.removeItem('idl_env'); - localStorage.removeItem('idl_env_exp'); - done(); - }, {adUnits}); - }); - it('test hook from identityLink cookie', function (done) { - coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 100000).toUTCString())); + // clear LS + localStorage.removeItem('amxId'); + localStorage.removeItem('amxId_exp'); + done(); + }, {adUnits}); + }); - setSubmoduleRegistry([identityLinkSubmodule]); - init(config); - config.setConfig(getConfigMock(['identityLink', 'idl_env', 'cookie'])); + it('test hook from identityLink html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('idl_env', 'AiGNC8Z5ONyZKSpIPf'); + localStorage.setItem('idl_env_exp', ''); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.idl_env'); - expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveramp.com', - uids: [{id: 'AiGNC8Z5ONyZKSpIPf', atype: 3}] + setSubmoduleRegistry([identityLinkSubmodule]); + init(config); + config.setConfig(getConfigMock(['identityLink', 'idl_env', 'html5'])); + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.idl_env'); + expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'liveramp.com', + uids: [{id: 'AiGNC8Z5ONyZKSpIPf', atype: 3}] + }); }); }); - }); - coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + localStorage.removeItem('idl_env'); + localStorage.removeItem('idl_env_exp'); + done(); + }, {adUnits}); + }); - it('test hook from criteoIdModule cookie', function (done) { - coreStorage.setCookie('storage_bidid', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 100000).toUTCString())); + it('test hook from identityLink cookie', function (done) { + coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 100000).toUTCString())); - setSubmoduleRegistry([criteoIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['criteo', 'storage_bidid', 'cookie'])); + setSubmoduleRegistry([identityLinkSubmodule]); + init(config); + config.setConfig(getConfigMock(['identityLink', 'idl_env', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.criteoId'); - expect(bid.userId.criteoId).to.equal('test_bidid'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'criteo.com', - uids: [{id: 'test_bidid', atype: 1}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.idl_env'); + expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'liveramp.com', + uids: [{id: 'AiGNC8Z5ONyZKSpIPf', atype: 3}] + }); }); }); - }); - coreStorage.setCookie('storage_bidid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from tapadIdModule cookie', function (done) { - coreStorage.setCookie('tapad_id', 'test-tapad-id', (new Date(Date.now() + 100000).toUTCString())); + it('test hook from criteoIdModule cookie', function (done) { + coreStorage.setCookie('storage_bidid', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 100000).toUTCString())); - setSubmoduleRegistry([tapadIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['tapadId', 'tapad_id', 'cookie'])); + setSubmoduleRegistry([criteoIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['criteo', 'storage_bidid', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.tapadId'); - expect(bid.userId.tapadId).to.equal('test-tapad-id'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'tapad.com', - uids: [{id: 'test-tapad-id', atype: 1}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.criteoId'); + expect(bid.userId.criteoId).to.equal('test_bidid'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'criteo.com', + uids: [{id: 'test_bidid', atype: 1}] + }); }); }); - }) - coreStorage.setCookie('tapad_id', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('storage_bidid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from liveIntentId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier'})); - localStorage.setItem('_li_pbid_exp', ''); + it('test hook from tapadIdModule cookie', function (done) { + coreStorage.setCookie('tapad_id', 'test-tapad-id', (new Date(Date.now() + 100000).toUTCString())); - setSubmoduleRegistry([liveIntentIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.lipb'); - expect(bid.userId.lipb.lipbid).to.equal('random-ls-identifier'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'random-ls-identifier', atype: 3}] + setSubmoduleRegistry([tapadIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['tapadId', 'tapad_id', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.tapadId'); + expect(bid.userId.tapadId).to.equal('test-tapad-id'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'tapad.com', + uids: [{id: 'test-tapad-id', atype: 1}] + }); + }); + }) + coreStorage.setCookie('tapad_id', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook from liveIntentId html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier'})); + localStorage.setItem('_li_pbid_exp', ''); + + setSubmoduleRegistry([liveIntentIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.lipb'); + expect(bid.userId.lipb.lipbid).to.equal('random-ls-identifier'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'liveintent.com', + uids: [{id: 'random-ls-identifier', atype: 3}] + }); }); }); - }); - localStorage.removeItem('_li_pbid'); - localStorage.removeItem('_li_pbid_exp'); - done(); - }, {adUnits}); - }); + localStorage.removeItem('_li_pbid'); + localStorage.removeItem('_li_pbid_exp'); + done(); + }, {adUnits}); + }); - it('test hook from liveIntentId cookie', function (done) { - coreStorage.setCookie('_li_pbid', JSON.stringify({'unifiedId': 'random-cookie-identifier'}), (new Date(Date.now() + 100000).toUTCString())); + it('test hook from Kinesso cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([liveIntentIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); + setSubmoduleRegistry([kinessoIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['kpuid', 'kpuid', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.lipb'); - expect(bid.userId.lipb.lipbid).to.equal('random-cookie-identifier'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'random-cookie-identifier', atype: 3}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.kpuid'); + expect(bid.userId.kpuid).to.equal('KINESSO_ID'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'kpuid.com', + uids: [{id: 'KINESSO_ID', atype: 3}] + }); }); }); - }); - coreStorage.setCookie('_li_pbid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('kpuid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from sharedId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611})); - localStorage.setItem('sharedid_exp', ''); + it('test hook from Kinesso html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('kpuid', 'KINESSO_ID'); + localStorage.setItem('kpuid_exp', ''); - setSubmoduleRegistry([sharedIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['sharedId', 'sharedid', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid).to.have.deep.nested.property('id'); - expect(bid.userId.sharedid).to.have.deep.nested.property('third'); - expect(bid.userId.sharedid).to.deep.equal({ - id: 'test_sharedId', - third: 'test_sharedId' - }); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'sharedid.org', - uids: [{ - id: 'test_sharedId', - atype: 1, - ext: { - third: 'test_sharedId' - } - }] + setSubmoduleRegistry([kinessoIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['kpuid', 'kpuid', 'html5'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.kpuid'); + expect(bid.userId.kpuid).to.equal('KINESSO_ID'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'kpuid.com', + uids: [{id: 'KINESSO_ID', atype: 3}] + }); }); }); - }); - localStorage.removeItem('sharedid'); - localStorage.removeItem('sharedid_exp'); - done(); - }, {adUnits}); - }); + localStorage.removeItem('kpuid'); + localStorage.removeItem('kpuid_exp', ''); + done(); + }, {adUnits}); + }); - it('test hook from sharedId html5 (id not synced)', function (done) { - // simulate existing browser local storage values - localStorage.setItem('sharedid', JSON.stringify({'id': 'test_sharedId', 'ns': true, 'ts': 1590525289611})); - localStorage.setItem('sharedid_exp', ''); + it('test hook from liveIntentId cookie', function (done) { + coreStorage.setCookie('_li_pbid', JSON.stringify({'unifiedId': 'random-cookie-identifier'}), (new Date(Date.now() + 100000).toUTCString())); - setSubmoduleRegistry([sharedIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['sharedId', 'sharedid', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid).to.have.deep.nested.property('id'); - expect(bid.userId.sharedid).to.deep.equal({ - id: 'test_sharedId' - }); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'sharedid.org', - uids: [{ - id: 'test_sharedId', - atype: 1 - }] + setSubmoduleRegistry([liveIntentIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.lipb'); + expect(bid.userId.lipb.lipbid).to.equal('random-cookie-identifier'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'liveintent.com', + uids: [{id: 'random-cookie-identifier', atype: 3}] + }); }); }); - }); - localStorage.removeItem('sharedid'); - localStorage.removeItem('sharedid_exp'); - done(); - }, {adUnits}); - }); - it('test hook from sharedId cookie', function (done) { - coreStorage.setCookie('sharedid', JSON.stringify({ - 'id': 'test_sharedId', - 'ts': 1590525289611 - }), (new Date(Date.now() + 100000).toUTCString())); + coreStorage.setCookie('_li_pbid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - setSubmoduleRegistry([sharedIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); + it('eidPermissions fun with bidders', function (done) { + coreStorage.setCookie('pubcid', 'test222', (new Date(Date.now() + 5000).toUTCString())); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid).to.have.deep.nested.property('id'); - expect(bid.userId.sharedid).to.have.deep.nested.property('third'); - expect(bid.userId.sharedid).to.deep.equal({ - id: 'test_sharedId', - third: 'test_sharedId' - }); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'sharedid.org', - uids: [{ - id: 'test_sharedId', - atype: 1, - ext: { - third: 'test_sharedId' + setSubmoduleRegistry([sharedIdSystemSubmodule]); + let eidPermissions; + getPrebidInternal().setEidPermissions = function (newEidPermissions) { + eidPermissions = newEidPermissions; + } + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'pubCommonId', + bidders: [ + 'sampleBidder' + ], + storage: { + type: 'cookie', + name: 'pubcid', + expires: 28 } - }] - }); - }); + } + ] + } }); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - it('test hook from sharedId cookie (id not synced) ', function (done) { - coreStorage.setCookie('sharedid', JSON.stringify({ - 'id': 'test_sharedId', - 'ns': true, - 'ts': 1590525289611 - }), (new Date(Date.now() + 100000).toUTCString())); - - setSubmoduleRegistry([sharedIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid).to.have.deep.nested.property('id'); - expect(bid.userId.sharedid).to.deep.equal({ - id: 'test_sharedId' - }); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'sharedid.org', - uids: [{ - id: 'test_sharedId', - atype: 1 - }] + requestBidsHook(function () { + expect(eidPermissions).to.deep.equal( + [ + { + bidders: [ + 'sampleBidder' + ], + source: 'pubcid.org' + } + ] + ); + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + if (bid.bidder === 'sampleBidder') { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('test222'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'pubcid.org', + uids: [ + { + id: 'test222', + atype: 1 + } + ] + }); + } + if (bid.bidder === 'anotherSampleBidder') { + expect(bid).to.not.have.deep.nested.property('userId.pubcid'); + expect(bid).to.not.have.property('userIdAsEids'); + } }); }); - }); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + getPrebidInternal().setEidPermissions = undefined; + done(); + }, {adUnits}); + }); - it('eidPermissions fun with bidders', function (done) { - coreStorage.setCookie('sharedid', JSON.stringify({ - 'id': 'test222', - 'ts': 1590525289611 - }), (new Date(Date.now() + 5000).toUTCString())); + it('eidPermissions fun without bidders', function (done) { + coreStorage.setCookie('pubcid', 'test222', new Date(Date.now() + 5000).toUTCString()); - setSubmoduleRegistry([sharedIdSubmodule]); - let eidPermissions; - getPrebidInternal().setEidPermissions = function (newEidPermissions) { - eidPermissions = newEidPermissions; - } - init(config); - config.setConfig({ - userSync: { - syncDelay: 0, - userIds: [ - { - name: 'sharedId', - bidders: [ - 'sampleBidder' - ], - storage: { - type: 'cookie', - name: 'sharedid', - expires: 28 - } - } - ] + setSubmoduleRegistry([sharedIdSystemSubmodule]); + let eidPermissions; + getPrebidInternal().setEidPermissions = function (newEidPermissions) { + eidPermissions = newEidPermissions; } - }); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'pubCommonId', + storage: { + type: 'cookie', + name: 'pubcid', + expires: 28 + } + } + ] + } + }); - requestBidsHook(function () { - expect(eidPermissions).to.deep.equal( - [ - { - bidders: [ - 'sampleBidder' - ], - source: 'sharedid.org' - } - ] - ); - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - if (bid.bidder === 'sampleBidder') { - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid.id).to.equal('test222'); + requestBidsHook(function () { + expect(eidPermissions).to.deep.equal( + [] + ); + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('test222'); expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'sharedid.org', + source: 'pubcid.org', uids: [ { id: 'test222', - atype: 1, - ext: { - third: 'test222' - } - } - ] + atype: 1 + }] }); - } - if (bid.bidder === 'anotherSampleBidder') { - expect(bid).to.not.have.deep.nested.property('userId.sharedid'); - expect(bid).to.not.have.property('userIdAsEids'); - } + }); }); - }); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - getPrebidInternal().setEidPermissions = undefined; - done(); - }, {adUnits}); - }); - - it('eidPermissions fun without bidders', function (done) { - coreStorage.setCookie('sharedid', JSON.stringify({ - 'id': 'test222', - 'ts': 1590525289611 - }), (new Date(Date.now() + 5000).toUTCString())); + getPrebidInternal().setEidPermissions = undefined; + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - setSubmoduleRegistry([sharedIdSubmodule]); - let eidPermissions; - getPrebidInternal().setEidPermissions = function (newEidPermissions) { - eidPermissions = newEidPermissions; - } - init(config); - config.setConfig({ - userSync: { - syncDelay: 0, - userIds: [ - { - name: 'sharedId', - storage: { - type: 'cookie', - name: 'sharedid', - expires: 28 + it('test hook from pubProvidedId config params', function (done) { + setSubmoduleRegistry([pubProvidedIdSubmodule]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [{ + name: 'pubProvidedId', + params: { + eids: [{ + source: 'example.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'ppuid' + } + }] + }, { + source: 'id-partner.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'dmp' + } + }] + }], + eidsFunction: function () { + return [{ + source: 'provider.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'sha256email' + } + }] + }] + } } } - ] - } - }); - - requestBidsHook(function () { - expect(eidPermissions).to.deep.equal( - [] - ); - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid.id).to.equal('test222'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'sharedid.org', - uids: [ - { - id: 'test222', - atype: 1, - ext: { - third: 'test222' - } - }] - }); - }); + ] + } }); - getPrebidInternal().setEidPermissions = undefined; - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - it('test hook from pubProvidedId config params', function (done) { - setSubmoduleRegistry([pubProvidedIdSubmodule]); - init(config); - config.setConfig({ - userSync: { - syncDelay: 0, - userIds: [{ - name: 'pubProvidedId', - params: { - eids: [{ + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubProvidedId'); + expect(bid.userId.pubProvidedId).to.deep.equal([{ source: 'example.com', uids: [{ id: 'value read from cookie or local storage', @@ -1388,1537 +1454,1204 @@ describe('User ID', function () { stype: 'dmp' } }] - }], - eidsFunction: function () { - return [{ - source: 'provider.com', - uids: [{ - id: 'value read from cookie or local storage', - ext: { - stype: 'sha256email' - } - }] + }, { + source: 'provider.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'sha256email' + } }] - } - } - } - ] - } - }); + }]); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.pubProvidedId'); - expect(bid.userId.pubProvidedId).to.deep.equal([{ - source: 'example.com', - uids: [{ - id: 'value read from cookie or local storage', - ext: { - stype: 'ppuid' - } - }] - }, { - source: 'id-partner.com', - uids: [{ - id: 'value read from cookie or local storage', - ext: { - stype: 'dmp' - } - }] - }, { - source: 'provider.com', - uids: [{ - id: 'value read from cookie or local storage', - ext: { - stype: 'sha256email' - } - }] - }]); - - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'example.com', - uids: [{ - id: 'value read from cookie or local storage', - ext: { - stype: 'ppuid' - } - }] - }); - expect(bid.userIdAsEids[2]).to.deep.equal({ - source: 'provider.com', - uids: [{ - id: 'value read from cookie or local storage', - ext: { - stype: 'sha256email' - } - }] + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'example.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'ppuid' + } + }] + }); + expect(bid.userIdAsEids[2]).to.deep.equal({ + source: 'provider.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'sha256email' + } + }] + }); }); }); - }); - done(); - }, {adUnits}); - }); + done(); + }, {adUnits}); + }); - it('test hook from liveIntentId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier', 'segments': ['123']})); - localStorage.setItem('_li_pbid_exp', ''); + it('test hook from liveIntentId html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier', 'segments': ['123']})); + localStorage.setItem('_li_pbid_exp', ''); - setSubmoduleRegistry([liveIntentIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.lipb'); - expect(bid.userId.lipb.lipbid).to.equal('random-ls-identifier'); - expect(bid.userId.lipb.segments).to.include('123'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'random-ls-identifier', atype: 3}], - ext: {segments: ['123']} + setSubmoduleRegistry([liveIntentIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.lipb'); + expect(bid.userId.lipb.lipbid).to.equal('random-ls-identifier'); + expect(bid.userId.lipb.segments).to.include('123'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'liveintent.com', + uids: [{id: 'random-ls-identifier', atype: 3}], + ext: {segments: ['123']} + }); }); }); - }); - localStorage.removeItem('_li_pbid'); - localStorage.removeItem('_li_pbid_exp'); - done(); - }, {adUnits}); - }); - - it('test hook from liveIntentId cookie', function (done) { - coreStorage.setCookie('_li_pbid', JSON.stringify({ - 'unifiedId': 'random-cookie-identifier', - 'segments': ['123'] - }), (new Date(Date.now() + 100000).toUTCString())); + localStorage.removeItem('_li_pbid'); + localStorage.removeItem('_li_pbid_exp'); + done(); + }, {adUnits}); + }); - setSubmoduleRegistry([liveIntentIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); + it('test hook from liveIntentId cookie', function (done) { + coreStorage.setCookie('_li_pbid', JSON.stringify({ + 'unifiedId': 'random-cookie-identifier', + 'segments': ['123'] + }), (new Date(Date.now() + 100000).toUTCString())); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.lipb'); - expect(bid.userId.lipb.lipbid).to.equal('random-cookie-identifier'); - expect(bid.userId.lipb.segments).to.include('123'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'random-cookie-identifier', atype: 3}], - ext: {segments: ['123']} + setSubmoduleRegistry([liveIntentIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.lipb'); + expect(bid.userId.lipb.lipbid).to.equal('random-cookie-identifier'); + expect(bid.userId.lipb.segments).to.include('123'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'liveintent.com', + uids: [{id: 'random-cookie-identifier', atype: 3}], + ext: {segments: ['123']} + }); }); }); - }); - coreStorage.setCookie('_li_pbid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('_li_pbid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from britepoolid cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': '279c0161-5152-487f-809e-05d7f7e653fd'}), (new Date(Date.now() + 5000).toUTCString())); + it('test hook from britepoolid cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': '279c0161-5152-487f-809e-05d7f7e653fd'}), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([britepoolIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['britepoolId', 'britepoolid', 'cookie'])); + setSubmoduleRegistry([britepoolIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['britepoolId', 'britepoolid', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.britepoolid'); - expect(bid.userId.britepoolid).to.equal('279c0161-5152-487f-809e-05d7f7e653fd'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'britepool.com', - uids: [{id: '279c0161-5152-487f-809e-05d7f7e653fd', atype: 3}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.britepoolid'); + expect(bid.userId.britepoolid).to.equal('279c0161-5152-487f-809e-05d7f7e653fd'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'britepool.com', + uids: [{id: '279c0161-5152-487f-809e-05d7f7e653fd', atype: 3}] + }); }); }); - }); - coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from dmdId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); + it('test hook from dmdId cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([dmdIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['dmdId', 'dmdId', 'cookie'])); + setSubmoduleRegistry([dmdIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['dmdId', 'dmdId', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.dmdId'); - expect(bid.userId.dmdId).to.equal('testdmdId'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'hcn.health', - uids: [{id: 'testdmdId', atype: 3}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.dmdId'); + expect(bid.userId.dmdId).to.equal('testdmdId'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'hcn.health', + uids: [{id: 'testdmdId', atype: 3}] + }); }); }); - }); - coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from netId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('netId', JSON.stringify({'netId': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg'}), (new Date(Date.now() + 5000).toUTCString())); + it('test hook from netId cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('netId', JSON.stringify({'netId': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg'}), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([netIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['netId', 'netId', 'cookie'])); + setSubmoduleRegistry([netIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['netId', 'netId', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.netId'); - expect(bid.userId.netId).to.equal('fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'netid.de', - uids: [{id: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', atype: 1}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.netId'); + expect(bid.userId.netId).to.equal('fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'netid.de', + uids: [{id: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', atype: 1}] + }); }); }); - }); - coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from intentIqId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('intentIqId', 'abcdefghijk', (new Date(Date.now() + 5000).toUTCString())); + it('test hook from intentIqId cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('intentIqId', 'abcdefghijk', (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([intentIqIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['intentIqId', 'intentIqId', 'cookie'])); + setSubmoduleRegistry([intentIqIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['intentIqId', 'intentIqId', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.intentIqId'); - expect(bid.userId.intentIqId).to.equal('abcdefghijk'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'intentiq.com', - uids: [{id: 'abcdefghijk', atype: 1}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.intentIqId'); + expect(bid.userId.intentIqId).to.equal('abcdefghijk'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'intentiq.com', + uids: [{id: 'abcdefghijk', atype: 1}] + }); }); }); - }); - coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from haloId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('haloId', JSON.stringify({'haloId': 'random-ls-identifier'})); - localStorage.setItem('haloId_exp', ''); + it('test hook from hadronId html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'random-ls-identifier'})); + localStorage.setItem('hadronId_exp', ''); - setSubmoduleRegistry([haloIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['haloId', 'haloId', 'html5'])); + setSubmoduleRegistry([hadronIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['hadronId', 'hadronId', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.haloId'); - expect(bid.userId.haloId).to.equal('random-ls-identifier'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'audigent.com', - uids: [{id: 'random-ls-identifier', atype: 1}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.hadronId'); + expect(bid.userId.hadronId).to.equal('random-ls-identifier'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'audigent.com', + uids: [{id: 'random-ls-identifier', atype: 1}] + }); }); }); - }); - localStorage.removeItem('haloId'); - localStorage.removeItem('haloId_exp', ''); - done(); - }, {adUnits}); - }); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp', ''); + done(); + }, {adUnits}); + }); - it('test hook from merkleId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('merkleId', JSON.stringify({'pam_id': {'id': 'testmerkleId', 'keyID': 1}}), (new Date(Date.now() + 5000).toUTCString())); + it('test hook from merkleId cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('merkleId', JSON.stringify({'pam_id': {'id': 'testmerkleId', 'keyID': 1}}), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([merkleIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['merkleId', 'merkleId', 'cookie'])); + setSubmoduleRegistry([merkleIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['merkleId', 'merkleId', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.merkleId'); - expect(bid.userId.merkleId).to.deep.equal({'id': 'testmerkleId', 'keyID': 1}); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'merkleinc.com', - uids: [{id: 'testmerkleId', atype: 3, ext: {keyID: 1}}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.merkleId'); + expect(bid.userId.merkleId).to.deep.equal({'id': 'testmerkleId', 'keyID': 1}); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'merkleinc.com', + uids: [{id: 'testmerkleId', atype: 3, ext: {keyID: 1}}] + }); }); }); - }); - coreStorage.setCookie('merkleId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('merkleId', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from zeotapIdPlus cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('IDP', btoa(JSON.stringify('abcdefghijk')), (new Date(Date.now() + 5000).toUTCString())); + it('test hook from zeotapIdPlus cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('IDP', btoa(JSON.stringify('abcdefghijk')), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([zeotapIdPlusSubmodule]); - init(config); - config.setConfig(getConfigMock(['zeotapIdPlus', 'IDP', 'cookie'])); + setSubmoduleRegistry([zeotapIdPlusSubmodule]); + init(config); + config.setConfig(getConfigMock(['zeotapIdPlus', 'IDP', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.IDP'); - expect(bid.userId.IDP).to.equal('abcdefghijk'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'zeotap.com', - uids: [{id: 'abcdefghijk', atype: 1}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.IDP'); + expect(bid.userId.IDP).to.equal('abcdefghijk'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'zeotap.com', + uids: [{id: 'abcdefghijk', atype: 1}] + }); }); }); - }); - coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from mwOpenLinkId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); + it('test hook from mwOpenLinkId cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([mwOpenLinkIdSubModule]); - init(config); - config.setConfig(getConfigMock(['mwOpenLinkId', 'mwol', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); - expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); - }); - }); - coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from admixerId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('admixerId', 'testadmixerId'); - localStorage.setItem('admixerId_exp', ''); + setSubmoduleRegistry([mwOpenLinkIdSubModule]); + init(config); + config.setConfig(getConfigMock(['mwOpenLinkId', 'mwol', 'cookie'])); - setSubmoduleRegistry([admixerIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['admixerId', 'admixerId', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.admixerId'); - expect(bid.userId.admixerId).to.equal('testadmixerId'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'admixer.net', - uids: [{id: 'testadmixerId', atype: 3}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); }); }); - }); - localStorage.removeItem('admixerId'); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from admixerId cookie', function (done) { - coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 100000).toUTCString())); + it('test hook from admixerId html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('admixerId', 'testadmixerId'); + localStorage.setItem('admixerId_exp', ''); - setSubmoduleRegistry([admixerIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['admixerId', 'admixerId', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.admixerId'); - expect(bid.userId.admixerId).to.equal('testadmixerId'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'admixer.net', - uids: [{id: 'testadmixerId', atype: 3}] + setSubmoduleRegistry([admixerIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['admixerId', 'admixerId', 'html5'])); + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.admixerId'); + expect(bid.userId.admixerId).to.equal('testadmixerId'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'admixer.net', + uids: [{id: 'testadmixerId', atype: 3}] + }); }); }); - }); - coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + localStorage.removeItem('admixerId'); + done(); + }, {adUnits}); + }); - it('test hook from deepintentId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); + it('test hook from admixerId cookie', function (done) { + coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 100000).toUTCString())); - setSubmoduleRegistry([deepintentDpesSubmodule]); - init(config); - config.setConfig(getConfigMock(['deepintentId', 'deepintentId', 'cookie'])); + setSubmoduleRegistry([admixerIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['admixerId', 'admixerId', 'cookie'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.deepintentId'); - expect(bid.userId.deepintentId).to.deep.equal('testdeepintentId'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'deepintent.com', - uids: [{id: 'testdeepintentId', atype: 3}] + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.admixerId'); + expect(bid.userId.admixerId).to.equal('testadmixerId'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'admixer.net', + uids: [{id: 'testadmixerId', atype: 3}] + }); }); }); - }); - coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from deepintentId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('deepintentId', 'testdeepintentId'); - localStorage.setItem('deepintentId_exp', ''); + it('test hook from deepintentId cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([deepintentDpesSubmodule]); - init(config); - config.setConfig(getConfigMock(['deepintentId', 'deepintentId', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.deepintentId'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'deepintent.com', - uids: [{id: 'testdeepintentId', atype: 3}] - }); - }); - }); - localStorage.removeItem('deepintentId'); - done(); - }, {adUnits}); - }); + setSubmoduleRegistry([deepintentDpesSubmodule]); + init(config); + config.setConfig(getConfigMock(['deepintentId', 'deepintentId', 'cookie'])); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId, Criteo, UID 2.0, admixerId, dmdId and mwOpenLinkId have data to pass', function (done) { - coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('sharedid', JSON.stringify({ - 'id': 'test_sharedId', - 'ts': 1590525289611 - }), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); - - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], - ['unifiedId', 'unifiedid', 'cookie'], - ['id5Id', 'id5id', 'cookie'], - ['identityLink', 'idl_env', 'cookie'], - ['britepoolId', 'britepoolid', 'cookie'], - ['dmdId', 'dmdId', 'cookie'], - ['netId', 'netId', 'cookie'], - ['sharedId', 'sharedid', 'cookie'], - ['intentIqId', 'intentIqId', 'cookie'], - ['zeotapIdPlus', 'IDP', 'cookie'], - ['haloId', 'haloId', 'cookie'], - ['criteo', 'storage_criteo', 'cookie'], - ['mwOpenLinkId', 'mwol', 'cookie'], - ['tapadId', 'tapad_id', 'cookie'], - ['uid2', 'uid2id', 'cookie'], - ['admixerId', 'admixerId', 'cookie'], - ['deepintentId', 'deepintentId', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - // verify that the PubCommonId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - // also check that UnifiedId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.tdid'); - expect(bid.userId.tdid).to.equal('testunifiedid'); - // also check that Id5Id id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.id5id.uid'); - expect(bid.userId.id5id.uid).to.equal('testid5id'); - // check that identityLink id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.idl_env'); - expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); - // also check that britepoolId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.britepoolid'); - expect(bid.userId.britepoolid).to.equal('testbritepoolid'); - // also check that dmdID id was copied to bid - expect(bid).to.have.deep.nested.property('userId.dmdId'); - expect(bid.userId.dmdId).to.equal('testdmdId'); - // also check that netId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.netId'); - expect(bid.userId.netId).to.equal('testnetId'); - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid).to.deep.equal({ - id: 'test_sharedId', - third: 'test_sharedId' - }); - // also check that intentIqId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.intentIqId'); - expect(bid.userId.intentIqId).to.equal('testintentIqId'); - // also check that zeotapIdPlus id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.IDP'); - expect(bid.userId.IDP).to.equal('zeotapId'); - // also check that haloId id was copied to bid - expect(bid).to.have.deep.nested.property('userId.haloId'); - expect(bid.userId.haloId).to.equal('testHaloId'); - // also check that criteo id was copied to bid - expect(bid).to.have.deep.nested.property('userId.criteoId'); - expect(bid.userId.criteoId).to.equal('test_bidid'); - // also check that mwOpenLink id was copied to bid - expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); - expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); - expect(bid.userId.uid2).to.deep.equal({ - id: 'Sample_AD_Token' + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.deepintentId'); + expect(bid.userId.deepintentId).to.deep.equal('testdeepintentId'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'deepintent.com', + uids: [{id: 'testdeepintentId', atype: 3}] + }); }); - // also check that criteo id was copied to bid - expect(bid).to.have.deep.nested.property('userId.admixerId'); - expect(bid.userId.admixerId).to.equal('testadmixerId'); - - // also check that deepintentId was copied to bid - expect(bid).to.have.deep.nested.property('userId.deepintentId'); - expect(bid.userId.deepintentId).to.equal('testdeepintentId'); - - expect(bid.userIdAsEids.length).to.equal(16); }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('id5id', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('deepintentId', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, dmdId, intentIqId, zeotapIdPlus, sharedId, criteo, netId, haloId, UID 2.0, admixerId and mwOpenLinkId have their modules added before and after init', function (done) { - coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('sharedid', JSON.stringify({ - 'id': 'test_sharedId', - 'ts': 1590525289611 - }), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); - - setSubmoduleRegistry([]); - - // attaching before init - attachIdSystem(pubCommonIdSubmodule); + it('test hook from deepintentId html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('deepintentId', 'testdeepintentId'); + localStorage.setItem('deepintentId_exp', ''); - init(config); - - // attaching after init - attachIdSystem(unifiedIdSubmodule); - attachIdSystem(id5IdSubmodule); - attachIdSystem(identityLinkSubmodule); - attachIdSystem(britepoolIdSubmodule); - attachIdSystem(netIdSubmodule); - attachIdSystem(sharedIdSubmodule); - attachIdSystem(intentIqIdSubmodule); - attachIdSystem(zeotapIdPlusSubmodule); - attachIdSystem(haloIdSubmodule); - attachIdSystem(dmdIdSubmodule); - attachIdSystem(criteoIdSubmodule); - attachIdSystem(mwOpenLinkIdSubModule); - attachIdSystem(tapadIdSubmodule); - attachIdSystem(uid2IdSubmodule); - attachIdSystem(admixerIdSubmodule); - attachIdSystem(deepintentDpesSubmodule); - - config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], - ['unifiedId', 'unifiedid', 'cookie'], - ['id5Id', 'id5id', 'cookie'], - ['identityLink', 'idl_env', 'cookie'], - ['britepoolId', 'britepoolid', 'cookie'], - ['netId', 'netId', 'cookie'], - ['sharedId', 'sharedid', 'cookie'], - ['intentIqId', 'intentIqId', 'cookie'], - ['zeotapIdPlus', 'IDP', 'cookie'], - ['haloId', 'haloId', 'cookie'], - ['dmdId', 'dmdId', 'cookie'], - ['criteo', 'storage_criteo', 'cookie'], - ['mwOpenLinkId', 'mwol', 'cookie'], - ['tapadId', 'tapad_id', 'cookie'], - ['uid2', 'uid2id', 'cookie'], - ['admixerId', 'admixerId', 'cookie'], - ['deepintentId', 'deepintentId', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - // verify that the PubCommonId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - // also check that UnifiedId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.tdid'); - expect(bid.userId.tdid).to.equal('cookie-value-add-module-variations'); - // also check that Id5Id id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.id5id.uid'); - expect(bid.userId.id5id.uid).to.equal('testid5id'); - // also check that identityLink id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.idl_env'); - expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); - // also check that britepoolId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.britepoolid'); - expect(bid.userId.britepoolid).to.equal('testbritepoolid'); - // also check that britepoolId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.netId'); - expect(bid.userId.netId).to.equal('testnetId'); - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid).to.deep.equal({ - id: 'test_sharedId', - third: 'test_sharedId' - }); - // also check that intentIqId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.intentIqId'); - expect(bid.userId.intentIqId).to.equal('testintentIqId'); - - // also check that zeotapIdPlus id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.IDP'); - expect(bid.userId.IDP).to.equal('zeotapId'); - // also check that haloId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.haloId'); - expect(bid.userId.haloId).to.equal('testHaloId'); - // also check that dmdId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.dmdId'); - expect(bid.userId.dmdId).to.equal('testdmdId'); - - // also check that criteo id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.criteoId'); - expect(bid.userId.criteoId).to.equal('test_bidid'); - - // also check that mwOpenLink id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); - expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123') - expect(bid.userId.uid2).to.deep.equal({ - id: 'Sample_AD_Token' + setSubmoduleRegistry([deepintentDpesSubmodule]); + init(config); + config.setConfig(getConfigMock(['deepintentId', 'deepintentId', 'html5'])); + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.deepintentId'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'deepintent.com', + uids: [{id: 'testdeepintentId', atype: 3}] + }); }); - - // also check that admixerId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.admixerId'); - expect(bid.userId.admixerId).to.equal('testadmixerId'); - // also check that deepintentId was copied to bid - expect(bid).to.have.deep.nested.property('userId.deepintentId'); - expect(bid.userId.deepintentId).to.equal('testdeepintentId'); - - expect(bid.userIdAsEids.length).to.equal(16); }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('id5id', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook when sharedId(opted out) have their modules added before and after init', function (done) { - coreStorage.setCookie('sharedid', JSON.stringify({ - 'id': '00000000000000000000000000', - 'ts': 1590525289611 - }), (new Date(Date.now() + 5000).toUTCString())); - - setSubmoduleRegistry([]); - init(config); - - attachIdSystem(sharedIdSubmodule); - - config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid.userIdAsEids).to.be.undefined; - }); - }); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test sharedid enabled via pubcid cookie', function (done) { - coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('pubcid_sharedid', 'testsharedid', (new Date(Date.now() + 5000).toUTCString())); - - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - - const customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); - addConfig(customCfg, 'params', {enableSharedId: true}); - config.setConfig(customCfg); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - // verify that the PubCommonId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - expect(findEid(bid.userIdAsEids, 'pubcid.org')).to.deep.equal( - {'source': 'pubcid.org', 'uids': [{'id': 'testpubcid', 'atype': 1}]} - ); - // verify that the sharedid was also copied to bid - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid).to.deep.equal({id: 'testsharedid'}); - expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.deep.equal( - {'source': 'sharedid.org', 'uids': [{'id': 'testsharedid', 'atype': 1}]} - ); - }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); - - done(); - }, {adUnits}); - }); - - it('test sharedid disabled via pubcid cookie', function (done) { - coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('pubcid_sharedid', 'testsharedid', (new Date(Date.now() + 5000).toUTCString())); - - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - - // pubCommonId's support for sharedId is off by default - config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - // verify that the PubCommonId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - expect(findEid(bid.userIdAsEids, 'pubcid.org')).to.deep.equal( - {'source': 'pubcid.org', 'uids': [{'id': 'testpubcid', 'atype': 1}]} - ); - // verify that the sharedid was not added to bid - expect(bid.userId).to.not.have.property('sharedid'); - expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.be.undefined; - }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); - - done(); - }, {adUnits}); - }); - - it('test sharedid enabled via pubcid html5', function (done) { - coreStorage.setDataInLocalStorage('pubcid', 'testpubcid'); - coreStorage.setDataInLocalStorage('pubcid_exp', new Date(Date.now() + 5000).toUTCString()); - coreStorage.setDataInLocalStorage('pubcid_sharedid', 'testsharedid'); - coreStorage.setDataInLocalStorage('pubcid_sharedid_exp', new Date(Date.now() + 5000).toUTCString()); - - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - - const customCfg = getConfigMock(['pubCommonId', 'pubcid', 'html5']); - addConfig(customCfg, 'params', {enableSharedId: true}); - config.setConfig(customCfg); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - // verify that the PubCommonId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - expect(findEid(bid.userIdAsEids, 'pubcid.org')).to.deep.equal( - {'source': 'pubcid.org', 'uids': [{'id': 'testpubcid', 'atype': 1}]} - ); - // verify that the sharedid was also copied to bid - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid).to.deep.equal({id: 'testsharedid'}); - expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.deep.equal( - {'source': 'sharedid.org', 'uids': [{'id': 'testsharedid', 'atype': 1}]} - ); - }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); - - coreStorage.removeDataFromLocalStorage('pubcid'); - coreStorage.removeDataFromLocalStorage('pubcid_exp'); - coreStorage.removeDataFromLocalStorage('pubcid_sharedid'); - coreStorage.removeDataFromLocalStorage('pubcid_sharedid_exp'); - done(); - }, {adUnits}); - }); - - it('test expired sharedid via pubcid html5', function (done) { - coreStorage.setDataInLocalStorage('pubcid', 'testpubcid'); - coreStorage.setDataInLocalStorage('pubcid_exp', new Date(Date.now() + 5000).toUTCString()); - - // set sharedid to expired already - coreStorage.setDataInLocalStorage('pubcid_sharedid', 'testsharedid'); - coreStorage.setDataInLocalStorage('pubcid_sharedid_exp', new Date(Date.now() - 100).toUTCString()); - - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - - const customCfg = getConfigMock(['pubCommonId', 'pubcid', 'html5']); - addConfig(customCfg, 'params', {enableSharedId: true}); - config.setConfig(customCfg); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - // verify that the PubCommonId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - expect(findEid(bid.userIdAsEids, 'pubcid.org')).to.deep.equal( - {'source': 'pubcid.org', 'uids': [{'id': 'testpubcid', 'atype': 1}]} - ); - // verify that the sharedid was not added - expect(bid.userId).to.not.have.property('sharedid'); - expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.be.undefined; - }); - }); - - coreStorage.removeDataFromLocalStorage('pubcid'); - coreStorage.removeDataFromLocalStorage('pubcid_exp'); - coreStorage.removeDataFromLocalStorage('pubcid_sharedid'); - coreStorage.removeDataFromLocalStorage('pubcid_sharedid_exp'); - done(); - }, {adUnits}); - }); - - it('test pubcid coexisting with sharedid', function (done) { - coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('pubcid_sharedid', 'test111', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('sharedid', JSON.stringify({ - 'id': 'test222', - 'ts': 1590525289611 - }), (new Date(Date.now() + 5000).toUTCString())); - - // When both pubcommon and sharedid are configured, pubcommon are invoked first due to - // module loading order. This mean the output from the primary sharedid module will overwrite - // the one in pubcommon. - - setSubmoduleRegistry([pubCommonIdSubmodule, sharedIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], - ['sharedId', 'sharedid', 'cookie'], - )); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - // verify that the PubCommonId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - expect(findEid(bid.userIdAsEids, 'pubcid.org')).to.deep.equal( - {'source': 'pubcid.org', 'uids': [{'id': 'testpubcid', 'atype': 1}]} - ); - // verify that the sharedid was also copied to bid - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid.id).to.equal('test222'); - expect(findEid(bid.userIdAsEids, 'sharedid.org').uids[0].id).to.equal('test222'); - }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - - done(); - }, {adUnits}); - }); + localStorage.removeItem('deepintentId'); + done(); + }, {adUnits}); + }); - it('test hook from UID2 cookie', function (done) { - coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); + it('test hook from qid html5', (done) => { + // simulate existing localStorage values + localStorage.setItem('qid', 'testqid'); + localStorage.setItem('qid_exp', ''); - setSubmoduleRegistry([uid2IdSubmodule]); - init(config); - config.setConfig(getConfigMock(['uid2', 'uid2id', 'cookie'])); + setSubmoduleRegistry([adqueryIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['qid', 'qid', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.uid2'); - expect(bid.userId.uid2).to.have.deep.nested.property('id'); - expect(bid.userId.uid2).to.deep.equal({ - id: 'Sample_AD_Token' - }); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'uidapi.com', - uids: [{ - id: 'Sample_AD_Token', - atype: 3, - }] + requestBidsHook(() => { + adUnits.forEach((adUnit) => { + adUnit.bids.forEach((bid) => { + expect(bid).to.have.deep.nested.property('userId.qid'); + expect(bid.userId.qid).to.equal('testqid'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'adquery.io', + uids: [{ + id: 'testqid', + atype: 1, + }] + }); }); }); - }); - coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - it('should add new id system ', function (done) { - coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('sharedid', JSON.stringify({ - 'id': 'test_sharedId', - 'ts': 1590525289611 - }), new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('admixerId', 'testadmixerId', new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('deepintentId', 'testdeepintentId', new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('__uid2_advertising_token', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule]); - init(config); - - config.setConfig({ - userSync: { - syncDelay: 0, - userIds: [{ - name: 'pubCommonId', storage: {name: 'pubcid', type: 'cookie'} - }, { - name: 'unifiedId', storage: {name: 'unifiedid', type: 'cookie'} - }, { - name: 'id5Id', storage: {name: 'id5id', type: 'cookie'} - }, { - name: 'identityLink', storage: {name: 'idl_env', type: 'cookie'} - }, { - name: 'britepoolId', storage: {name: 'britepoolid', type: 'cookie'} - }, { - name: 'dmdId', storage: {name: 'dmdId', type: 'cookie'} - }, { - name: 'netId', storage: {name: 'netId', type: 'cookie'} - }, { - name: 'sharedId', storage: {name: 'sharedid', type: 'cookie'} - }, { - name: 'intentIqId', storage: {name: 'intentIqId', type: 'cookie'} - }, { - name: 'zeotapIdPlus' - }, { - name: 'haloId', storage: {name: 'haloId', type: 'cookie'} - }, { - name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} - }, { - name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} - }, { - name: 'uid2' - }, { - name: 'deepintentId', storage: {name: 'deepintentId', type: 'cookie'} - }] - } - }); - - // Add new submodule named 'mockId' - attachIdSystem({ - name: 'mockId', - decode: function (value) { - return { - 'mid': value['MOCKID'] - }; - }, - getId: function (config, storedId) { - if (storedId) return {}; - return {id: {'MOCKID': '1234'}}; - } - }); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - // check PubCommonId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - // check UnifiedId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.tdid'); - expect(bid.userId.tdid).to.equal('cookie-value-add-module-variations'); - // also check that Id5Id id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.id5id.uid'); - expect(bid.userId.id5id.uid).to.equal('testid5id'); - // also check that identityLink id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.idl_env'); - expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); - // also check that britepoolId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.britepoolid'); - expect(bid.userId.britepoolid).to.equal('testbritepoolid'); - // also check that dmdId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.dmdId'); - expect(bid.userId.dmdId).to.equal('testdmdId'); - // check MockId data was copied to bid - expect(bid).to.have.deep.nested.property('userId.netId'); - expect(bid.userId.netId).to.equal('testnetId'); - // also check that sharedId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid).to.deep.equal({ - id: 'test_sharedId', - third: 'test_sharedId' - }); - // check MockId data was copied to bid - expect(bid).to.have.deep.nested.property('userId.mid'); - expect(bid.userId.mid).to.equal('123456778'); - // also check that intentIqId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.intentIqId'); - expect(bid.userId.intentIqId).to.equal('testintentIqId'); - // also check that zeotapIdPlus id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.IDP'); - expect(bid.userId.IDP).to.equal('zeotapId'); - // also check that haloId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.haloId'); - expect(bid.userId.haloId).to.equal('testHaloId'); - expect(bid.userId.uid2).to.deep.equal({ - id: 'Sample_AD_Token' - }); - - // also check that admixerId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.admixerId'); - expect(bid.userId.admixerId).to.equal('testadmixerId'); - - // also check that deepintentId was copied to bid - expect(bid).to.have.deep.nested.property('userId.deepintentId'); - expect(bid.userId.deepintentId).to.equal('testdeepintentId'); - expect(bid.userIdAsEids.length).to.equal(14); - }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('id5id', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - }); + // clear LS + localStorage.removeItem('qid'); + localStorage.removeItem('qid_exp'); + done(); + }, {adUnits}); + }); - describe('callbacks at the end of auction', function () { - beforeEach(function () { - sinon.stub(events, 'getEvents').returns([]); - sinon.stub(utils, 'triggerPixel'); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); - }); + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, netId, hadronId, Criteo, UID 2.0, admixerId, amxId, dmdId, kpuid, qid and mwOpenLinkId have data to pass', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); + // amxId only supports localStorage + localStorage.setItem('amxId', 'test_amxid_id'); + localStorage.setItem('amxId_exp', (new Date(Date.now() + 5000)).toUTCString()); + // qid only supports localStorage + localStorage.setItem('qid', 'testqid'); + localStorage.setItem('qid_exp', (new Date(Date.now() + 5000)).toUTCString()); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], + ['unifiedId', 'unifiedid', 'cookie'], + ['id5Id', 'id5id', 'cookie'], + ['identityLink', 'idl_env', 'cookie'], + ['britepoolId', 'britepoolid', 'cookie'], + ['dmdId', 'dmdId', 'cookie'], + ['netId', 'netId', 'cookie'], + ['intentIqId', 'intentIqId', 'cookie'], + ['zeotapIdPlus', 'IDP', 'cookie'], + ['hadronId', 'hadronId', 'cookie'], + ['criteo', 'storage_criteo', 'cookie'], + ['mwOpenLinkId', 'mwol', 'cookie'], + ['tapadId', 'tapad_id', 'cookie'], + ['uid2', 'uid2id', 'cookie'], + ['admixerId', 'admixerId', 'cookie'], + ['amxId', 'amxId', 'html5'], + ['deepintentId', 'deepintentId', 'cookie'], + ['kpuid', 'kpuid', 'cookie'], + ['qid', 'qid', 'html5'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + // verify that the PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + // also check that UnifiedId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.tdid'); + expect(bid.userId.tdid).to.equal('testunifiedid'); + // also check that Id5Id id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.id5id.uid'); + expect(bid.userId.id5id.uid).to.equal('testid5id'); + // check that identityLink id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.idl_env'); + expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + // also check that britepoolId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.britepoolid'); + expect(bid.userId.britepoolid).to.equal('testbritepoolid'); + // also check that dmdID id was copied to bid + expect(bid).to.have.deep.nested.property('userId.dmdId'); + expect(bid.userId.dmdId).to.equal('testdmdId'); + // also check that netId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.netId'); + expect(bid.userId.netId).to.equal('testnetId'); + // also check that intentIqId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.intentIqId'); + expect(bid.userId.intentIqId).to.equal('testintentIqId'); + // also check that zeotapIdPlus id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.IDP'); + expect(bid.userId.IDP).to.equal('zeotapId'); + // also check that hadronId id was copied to bid + expect(bid).to.have.deep.nested.property('userId.hadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId'); + // also check that criteo id was copied to bid + expect(bid).to.have.deep.nested.property('userId.criteoId'); + expect(bid.userId.criteoId).to.equal('test_bidid'); + // also check that mwOpenLink id was copied to bid + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); + expect(bid).to.have.deep.nested.property('userId.amxId'); + expect(bid.userId.amxId).to.equal('test_amxid_id'); - afterEach(function () { - events.getEvents.restore(); - utils.triggerPixel.restore(); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); - resetConsentData(); - delete window.__tcfapi; - }); + // also check that criteo id was copied to bid + expect(bid).to.have.deep.nested.property('userId.admixerId'); + expect(bid.userId.admixerId).to.equal('testadmixerId'); - it('pubcid callback with url', function () { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); - customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url'}); + // also check that deepintentId was copied to bid + expect(bid).to.have.deep.nested.property('userId.deepintentId'); + expect(bid.userId.deepintentId).to.equal('testdeepintentId'); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); - init(config); - config.setConfig(customCfg); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + expect(bid).to.have.deep.nested.property('userId.kpuid'); + expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - expect(utils.triggerPixel.called).to.be.false; - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); - expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); - }); + expect(bid).to.have.deep.nested.property('userId.qid'); + expect(bid.userId.qid).to.equal('testqid'); - it('unifiedid callback with url', function () { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); - addConfig(customCfg, 'params', {url: '/any/unifiedid/url'}); + expect(bid.userIdAsEids.length).to.equal(18); + }); + }); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('id5id', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('deepintentId', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('kpuid', EXPIRED_COOKIE_DATE); + localStorage.removeItem('amxId'); + localStorage.removeItem('amxId_exp'); + localStorage.removeItem('qid'); + localStorage.removeItem('qid_exp'); + done(); + }, {adUnits}); + }); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); - init(config); - config.setConfig(customCfg); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, dmdId, intentIqId, zeotapIdPlus, criteo, netId, hadronId, UID 2.0, admixerId, kpuid and mwOpenLinkId have their modules added before and after init', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([]); + + // attaching before init + attachIdSystem(sharedIdSystemSubmodule); - expect(server.requests).to.be.empty; - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); - expect(server.requests[0].url).to.equal('/any/unifiedid/url'); - }); + init(config); - it('unifiedid callback with partner', function () { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); - addConfig(customCfg, 'params', {partner: 'rubicon'}); + // attaching after init + attachIdSystem(unifiedIdSubmodule); + attachIdSystem(id5IdSubmodule); + attachIdSystem(identityLinkSubmodule); + attachIdSystem(britepoolIdSubmodule); + attachIdSystem(netIdSubmodule); + attachIdSystem(intentIqIdSubmodule); + attachIdSystem(zeotapIdPlusSubmodule); + attachIdSystem(hadronIdSubmodule); + attachIdSystem(dmdIdSubmodule); + attachIdSystem(criteoIdSubmodule); + attachIdSystem(mwOpenLinkIdSubModule); + attachIdSystem(tapadIdSubmodule); + attachIdSystem(uid2IdSubmodule); + attachIdSystem(admixerIdSubmodule); + attachIdSystem(deepintentDpesSubmodule); + attachIdSystem(kinessoIdSubmodule); + + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], + ['unifiedId', 'unifiedid', 'cookie'], + ['id5Id', 'id5id', 'cookie'], + ['identityLink', 'idl_env', 'cookie'], + ['britepoolId', 'britepoolid', 'cookie'], + ['netId', 'netId', 'cookie'], + ['intentIqId', 'intentIqId', 'cookie'], + ['zeotapIdPlus', 'IDP', 'cookie'], + ['hadronId', 'hadronId', 'cookie'], + ['dmdId', 'dmdId', 'cookie'], + ['criteo', 'storage_criteo', 'cookie'], + ['mwOpenLinkId', 'mwol', 'cookie'], + ['tapadId', 'tapad_id', 'cookie'], + ['uid2', 'uid2id', 'cookie'], + ['admixerId', 'admixerId', 'cookie'], + ['deepintentId', 'deepintentId', 'cookie'], + ['kpuid', 'kpuid', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + // verify that the PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + // also check that UnifiedId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.tdid'); + expect(bid.userId.tdid).to.equal('cookie-value-add-module-variations'); + // also check that Id5Id id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.id5id.uid'); + expect(bid.userId.id5id.uid).to.equal('testid5id'); + // also check that identityLink id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.idl_env'); + expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + // also check that britepoolId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.britepoolid'); + expect(bid.userId.britepoolid).to.equal('testbritepoolid'); + // also check that britepoolId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.netId'); + expect(bid.userId.netId).to.equal('testnetId'); + // also check that intentIqId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.intentIqId'); + expect(bid.userId.intentIqId).to.equal('testintentIqId'); + + // also check that zeotapIdPlus id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.IDP'); + expect(bid.userId.IDP).to.equal('zeotapId'); + // also check that hadronId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.hadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId'); + // also check that dmdId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.dmdId'); + expect(bid.userId.dmdId).to.equal('testdmdId'); + + // also check that criteo id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.criteoId'); + expect(bid.userId.criteoId).to.equal('test_bidid'); + + // also check that mwOpenLink id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123') + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); - init(config); - config.setConfig(customCfg); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + // also check that admixerId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.admixerId'); + expect(bid.userId.admixerId).to.equal('testadmixerId'); + // also check that deepintentId was copied to bid + expect(bid).to.have.deep.nested.property('userId.deepintentId'); + expect(bid.userId.deepintentId).to.equal('testdeepintentId'); + expect(bid).to.have.deep.nested.property('userId.kpuid'); + expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - expect(server.requests).to.be.empty; - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); - expect(server.requests[0].url).to.equal('https://match.adsrvr.org/track/rid?ttd_pid=rubicon&fmt=json'); - }); + expect(bid.userIdAsEids.length).to.equal(16); + }); + }); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('id5id', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('kpuid', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('verify sharedid callback via pubcid when enabled', function () { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); - customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url', enableSharedId: true}); + it('test hook from UID2 cookie', function (done) { + coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - server.respondWith('https://id.sharedid.org/id', function(xhr) { - xhr.respond(200, {}, '{"sharedId":"testsharedid"}'); + setSubmoduleRegistry([uid2IdSubmodule]); + init(config); + config.setConfig(getConfigMock(['uid2', 'uid2id', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.uid2'); + expect(bid.userId.uid2).to.have.deep.nested.property('id'); + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: 'Sample_AD_Token', + atype: 3, + }] + }); + }); + }); + coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); }); - server.respondImmediately = true; + it('should add new id system ', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('admixerId', 'testadmixerId', new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('deepintentId', 'testdeepintentId', new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('__uid2_advertising_token', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); + localStorage.setItem('amxId', 'test_amxid_id'); + localStorage.setItem('amxId_exp', new Date(Date.now() + 5000).toUTCString()) + coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); + localStorage.setItem('qid', 'testqid'); + localStorage.setItem('qid_exp', new Date(Date.now() + 5000).toUTCString()) + + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + init(config); - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - config.setConfig(customCfg); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [{ + name: 'pubCommonId', storage: {name: 'pubcid', type: 'cookie'} + }, { + name: 'unifiedId', storage: {name: 'unifiedid', type: 'cookie'} + }, { + name: 'id5Id', storage: {name: 'id5id', type: 'cookie'} + }, { + name: 'identityLink', storage: {name: 'idl_env', type: 'cookie'} + }, { + name: 'britepoolId', storage: {name: 'britepoolid', type: 'cookie'} + }, { + name: 'dmdId', storage: {name: 'dmdId', type: 'cookie'} + }, { + name: 'netId', storage: {name: 'netId', type: 'cookie'} + }, { + name: 'intentIqId', storage: {name: 'intentIqId', type: 'cookie'} + }, { + name: 'zeotapIdPlus' + }, { + name: 'hadronId', storage: {name: 'hadronId', type: 'cookie'} + }, { + name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} + }, { + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} + }, { + name: 'uid2' + }, { + name: 'deepintentId', storage: {name: 'deepintentId', type: 'cookie'} + }, { + name: 'amxId', storage: {name: 'amxId', type: 'html5'} + }, { + name: 'kpuid', storage: {name: 'kpuid', type: 'cookie'} + }, { + name: 'qid', storage: {name: 'qid', type: 'html5'} + }] + } + }); - expect(utils.triggerPixel.called).to.be.false; - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); - expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); + // Add new submodule named 'mockId' + attachIdSystem({ + name: 'mockId', + decode: function (value) { + return { + 'mid': value['MOCKID'] + }; + }, + getId: function (config, storedId) { + if (storedId) return {}; + return {id: {'MOCKID': '1234'}}; + } + }); - expect(server.requests[0].url).to.equal('https://id.sharedid.org/id'); - expect(coreStorage.getCookie('pubcid_sharedid')).to.equal('testsharedid'); - }); + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + // check PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + // check UnifiedId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.tdid'); + expect(bid.userId.tdid).to.equal('cookie-value-add-module-variations'); + // also check that Id5Id id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.id5id.uid'); + expect(bid.userId.id5id.uid).to.equal('testid5id'); + // also check that identityLink id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.idl_env'); + expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + // also check that britepoolId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.britepoolid'); + expect(bid.userId.britepoolid).to.equal('testbritepoolid'); + // also check that dmdId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.dmdId'); + expect(bid.userId.dmdId).to.equal('testdmdId'); + // check MockId data was copied to bid + expect(bid).to.have.deep.nested.property('userId.netId'); + expect(bid.userId.netId).to.equal('testnetId'); + // check MockId data was copied to bid + expect(bid).to.have.deep.nested.property('userId.mid'); + expect(bid.userId.mid).to.equal('1234'); + // also check that intentIqId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.intentIqId'); + expect(bid.userId.intentIqId).to.equal('testintentIqId'); + // also check that zeotapIdPlus id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.IDP'); + expect(bid.userId.IDP).to.equal('zeotapId'); + // also check that hadronId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.hadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId'); + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); - it('verify no sharedid callback via pubcid when disabled', function () { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); - customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url'}); + expect(bid).to.have.deep.nested.property('userId.amxId'); + expect(bid.userId.amxId).to.equal('test_amxid_id'); - server.respondWith('https://id.sharedid.org/id', function(xhr) { - xhr.respond(200, {}, '{"sharedId":"testsharedid"}'); - }); - server.respondImmediately = true; + // also check that admixerId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.admixerId'); + expect(bid.userId.admixerId).to.equal('testadmixerId'); - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - config.setConfig(customCfg); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + // also check that deepintentId was copied to bid + expect(bid).to.have.deep.nested.property('userId.deepintentId'); + expect(bid.userId.deepintentId).to.equal('testdeepintentId'); - expect(utils.triggerPixel.called).to.be.false; - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); - expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); + expect(bid).to.have.deep.nested.property('userId.kpuid'); + expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - expect(server.requests).to.have.lengthOf(0); - expect(coreStorage.getCookie('pubcid_sharedid')).to.be.null; + expect(bid).to.have.deep.nested.property('userId.qid'); + expect(bid.userId.qid).to.equal('testqid'); + expect(bid.userIdAsEids.length).to.equal(16); + }); + }); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('id5id', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('amxId'); + localStorage.removeItem('amxId_exp'); + coreStorage.setCookie('kpuid', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); }); - it('verify sharedid optout via pubcid when enabled', function () { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); - customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url', enableSharedId: true}); - coreStorage.setCookie('pubcid_sharedid', 'testsharedid', (new Date(Date.now() + 5000).toUTCString())); + describe('callbacks at the end of auction', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + sinon.stub(utils, 'triggerPixel'); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); + }); - server.respondWith('https://id.sharedid.org/id', function(xhr) { - xhr.respond(200, {}, '{"sharedId":"00000000000000000000000000"}'); + afterEach(function () { + events.getEvents.restore(); + utils.triggerPixel.restore(); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); + resetConsentData(); + delete window.__tcfapi; }); - server.respondImmediately = true; - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - config.setConfig(customCfg); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + it('pubcid callback with url', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); + customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url'}); - expect(utils.triggerPixel.called).to.be.false; - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); - expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); - expect(server.requests[0].url).to.equal('https://id.sharedid.org/id'); - expect(coreStorage.getCookie('pubcid_sharedid')).to.be.null; - }); + expect(utils.triggerPixel.called).to.be.false; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); + }); - it('verify sharedid called with consent data when gdpr applies', function () { - let adUnits = [getAdUnitMock()]; - let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); - let consentConfig = { - cmpApi: 'iab', - timeout: 7500, - allowAuctionWithoutConsent: false - }; - customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url', enableSharedId: true}); - - server.respondWith('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234', function(xhr) { - xhr.respond(200, {}, '{"sharedId":"testsharedid"}'); - }); - server.respondImmediately = true; - - let testConsentData = { - tcString: 'abc12345234', - gdprApplies: true, - purposeOneTreatment: false, - eventStatus: 'tcloaded', - vendor: {consents: {887: true}}, - purpose: { - consents: { - 1: true - } - } - }; + it('unifiedid callback with url', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); + addConfig(customCfg, 'params', {url: '/any/unifiedid/url'}); - window.__tcfapi = function () { }; - sinon.stub(window, '__tcfapi').callsFake((...args) => { - args[2](testConsentData, true); - }); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - config.setConfig(customCfg); - setConsentConfig(consentConfig); + expect(server.requests).to.be.empty; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + expect(server.requests[0].url).to.equal('/any/unifiedid/url'); + }); - consentManagementRequestBidsHook(() => { - }, {}); - requestBidsHook((config) => { - }, {adUnits}); + it('unifiedid callback with partner', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); + addConfig(customCfg, 'params', {partner: 'rubicon'}); - expect(utils.triggerPixel.called).to.be.false; - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); - expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); - expect(server.requests[0].url).to.equal('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234'); - expect(coreStorage.getCookie('pubcid_sharedid')).to.equal('testsharedid'); + expect(server.requests).to.be.empty; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + expect(server.requests[0].url).to.equal('https://match.adsrvr.org/track/rid?ttd_pid=rubicon&fmt=json'); + }); }); - }); - describe('Set cookie behavior', function () { - let coreStorageSpy; - beforeEach(function () { - coreStorageSpy = sinon.spy(coreStorage, 'setCookie'); - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - }); - afterEach(function () { - coreStorageSpy.restore(); - }); - it('should allow submodules to override the domain', function () { - const submodule = { - submodule: { - domainOverride: function () { - return 'foo.com' - } - }, - config: { - storage: { - type: 'cookie' + describe('Set cookie behavior', function () { + let coreStorageSpy; + beforeEach(function () { + coreStorageSpy = sinon.spy(coreStorage, 'setCookie'); + setSubmoduleRegistry([sharedIdSystemSubmodule]); + init(config); + }); + afterEach(function () { + coreStorageSpy.restore(); + }); + it('should allow submodules to override the domain', function () { + const submodule = { + submodule: { + domainOverride: function () { + return 'foo.com' + } + }, + config: { + storage: { + type: 'cookie' + } } } - } - setStoredValue(submodule, 'bar'); - expect(coreStorage.setCookie.getCall(0).args[4]).to.equal('foo.com'); - }); + setStoredValue(submodule, 'bar'); + expect(coreStorage.setCookie.getCall(0).args[4]).to.equal('foo.com'); + }); - it('should pass null for domain if submodule does not override the domain', function () { - const submodule = { - submodule: {}, - config: { - storage: { - type: 'cookie' + it('should pass null for domain if submodule does not override the domain', function () { + const submodule = { + submodule: {}, + config: { + storage: { + type: 'cookie' + } } } - } - setStoredValue(submodule, 'bar'); - expect(coreStorage.setCookie.getCall(0).args[4]).to.equal(null); + setStoredValue(submodule, 'bar'); + expect(coreStorage.setCookie.getCall(0).args[4]).to.equal(null); + }); }); - }); - describe('Consent changes determine getId refreshes', function () { - let expStr; - let adUnits; - - const mockIdCookieName = 'MOCKID'; - let mockGetId = sinon.stub(); - let mockDecode = sinon.stub(); - let mockExtendId = sinon.stub(); - const mockIdSystem = { - name: 'mockId', - getId: mockGetId, - decode: mockDecode, - extendId: mockExtendId - }; - const userIdConfig = { - userSync: { - userIds: [{ - name: 'mockId', - storage: { - name: 'MOCKID', - type: 'cookie', - refreshInSeconds: 30 - } - }], - auctionDelay: 5 - } - }; + describe('Consent changes determine getId refreshes', function () { + let expStr; + let adUnits; - let cmpStub; - let testConsentData; - const consentConfig = { - cmpApi: 'iab', - timeout: 7500, - allowAuctionWithoutConsent: false - }; + const mockIdCookieName = 'MOCKID'; + let mockGetId = sinon.stub(); + let mockDecode = sinon.stub(); + let mockExtendId = sinon.stub(); + const mockIdSystem = { + name: 'mockId', + getId: mockGetId, + decode: mockDecode, + extendId: mockExtendId + }; + const userIdConfig = { + userSync: { + userIds: [{ + name: 'mockId', + storage: { + name: 'MOCKID', + type: 'cookie', + refreshInSeconds: 30 + } + }], + auctionDelay: 5 + } + }; - const sharedBeforeFunction = function () { - // clear cookies - expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(mockIdCookieName, '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie(`${mockIdCookieName}_last`, '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie(CONSENT_LOCAL_STORAGE_NAME, '', EXPIRED_COOKIE_DATE); + let cmpStub; + let testConsentData; + const consentConfig = { + cmpApi: 'iab', + timeout: 7500, + allowAuctionWithoutConsent: false + }; - // init - adUnits = [getAdUnitMock()]; - init(config); + const sharedBeforeFunction = function () { + // clear cookies + expStr = (new Date(Date.now() + 25000).toUTCString()); + coreStorage.setCookie(mockIdCookieName, '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie(`${mockIdCookieName}_last`, '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie(CONSENT_LOCAL_STORAGE_NAME, '', EXPIRED_COOKIE_DATE); - // init id system - attachIdSystem(mockIdSystem); - config.setConfig(userIdConfig); - } - const sharedAfterFunction = function () { - config.resetConfig(); - mockGetId.reset(); - mockDecode.reset(); - mockExtendId.reset(); - cmpStub.restore(); - resetConsentData(); - delete window.__cmp; - delete window.__tcfapi; - }; + // init + adUnits = [getAdUnitMock()]; + init(config); - describe('TCF v1', function () { - testConsentData = { - gdprApplies: true, - consentData: 'xyz', - apiVersion: 1 + // init id system + attachIdSystem(mockIdSystem); + config.setConfig(userIdConfig); + } + const sharedAfterFunction = function () { + config.resetConfig(); + mockGetId.reset(); + mockDecode.reset(); + mockExtendId.reset(); + cmpStub.restore(); + resetConsentData(); + delete window.__cmp; + delete window.__tcfapi; }; - beforeEach(function () { - sharedBeforeFunction(); - - // init v1 consent management - window.__cmp = function () { + describe('TCF v1', function () { + testConsentData = { + gdprApplies: true, + consentData: 'xyz', + apiVersion: 1 }; - delete window.__tcfapi; - cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { - args[2](testConsentData); - }); - setConsentConfig(consentConfig); - }); - afterEach(function () { - sharedAfterFunction(); - }); + beforeEach(function () { + sharedBeforeFunction(); - it('does not call getId if no stored consent data and refresh is not needed', function () { - coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); - coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); + // init v1 consent management + window.__cmp = function () { + }; + delete window.__tcfapi; + cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { + args[2](testConsentData); + }); + setConsentConfig(consentConfig); + }); - let innerAdUnits; - consentManagementRequestBidsHook(() => { - }, {}); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + afterEach(function () { + sharedAfterFunction(); + }); - sinon.assert.notCalled(mockGetId); - sinon.assert.calledOnce(mockDecode); - sinon.assert.calledOnce(mockExtendId); - }); + it('calls getId if no stored consent data and refresh is not needed', function () { + coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); + coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); - it('calls getId if no stored consent data but refresh is needed', function () { - coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); - coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 60 * 1000).toUTCString()), expStr); + let innerAdUnits; + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); - let innerAdUnits; - consentManagementRequestBidsHook(() => { - }, {}); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + sinon.assert.calledOnce(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.notCalled(mockExtendId); + }); - sinon.assert.calledOnce(mockGetId); - sinon.assert.calledOnce(mockDecode); - sinon.assert.notCalled(mockExtendId); - }); + it('calls getId if no stored consent data but refresh is needed', function () { + coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); + coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 60 * 1000).toUTCString()), expStr); - it('calls getId if empty stored consent and refresh not needed', function () { - coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); - coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); + let innerAdUnits; + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); - setStoredConsentData(); + sinon.assert.calledOnce(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.notCalled(mockExtendId); + }); - let innerAdUnits; - consentManagementRequestBidsHook(() => { - }, {}); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + it('calls getId if empty stored consent and refresh not needed', function () { + coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); + coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); - sinon.assert.calledOnce(mockGetId); - sinon.assert.calledOnce(mockDecode); - sinon.assert.notCalled(mockExtendId); - }); + setStoredConsentData(); - it('calls getId if stored consent does not match current consent and refresh not needed', function () { - coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); - coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); + let innerAdUnits; + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); - setStoredConsentData({ - gdprApplies: testConsentData.gdprApplies, - consentString: 'abc', - apiVersion: testConsentData.apiVersion + sinon.assert.calledOnce(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.notCalled(mockExtendId); }); - let innerAdUnits; - consentManagementRequestBidsHook(() => { - }, {}); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + it('calls getId if stored consent does not match current consent and refresh not needed', function () { + coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); + coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); - sinon.assert.calledOnce(mockGetId); - sinon.assert.calledOnce(mockDecode); - sinon.assert.notCalled(mockExtendId); - }); + setStoredConsentData({ + gdprApplies: testConsentData.gdprApplies, + consentString: 'abc', + apiVersion: testConsentData.apiVersion + }); - it('does not call getId if stored consent matches current consent and refresh not needed', function () { - coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); - coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); + let innerAdUnits; + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); - setStoredConsentData({ - gdprApplies: testConsentData.gdprApplies, - consentString: testConsentData.consentData, - apiVersion: testConsentData.apiVersion + sinon.assert.calledOnce(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.notCalled(mockExtendId); }); - let innerAdUnits; - consentManagementRequestBidsHook(() => { - }, {}); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + it('does not call getId if stored consent matches current consent and refresh not needed', function () { + coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); + coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); - sinon.assert.notCalled(mockGetId); - sinon.assert.calledOnce(mockDecode); - sinon.assert.calledOnce(mockExtendId); - }); - }); + setStoredConsentData({ + gdprApplies: testConsentData.gdprApplies, + consentString: testConsentData.consentData, + apiVersion: testConsentData.apiVersion + }); - describe('findRootDomain', function () { - let sandbox; + let innerAdUnits; + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); - beforeEach(function () { - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - config.setConfig({ - userSync: { - syncDelay: 0, - userIds: [ - { - name: 'pubCommonId', - value: { pubcid: '11111' }, - }, - ], - }, + sinon.assert.notCalled(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.calledOnce(mockExtendId); }); - sandbox = sinon.createSandbox(); - sandbox - .stub(coreStorage, 'getCookie') - .onFirstCall() - .returns(null) // .co.uk - .onSecondCall() - .returns('writeable'); // realdomain.co.uk; }); - afterEach(function () { - sandbox.restore(); - }); + describe('findRootDomain', function () { + let sandbox; - it('should just find the root domain', function () { - var domain = findRootDomain('sub.realdomain.co.uk'); - expect(domain).to.be.eq('realdomain.co.uk'); - }); + beforeEach(function () { + setSubmoduleRegistry([sharedIdSystemSubmodule]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'pubCommonId', + value: { pubcid: '11111' }, + }, + ], + }, + }); + sandbox = sinon.createSandbox(); + sandbox + .stub(coreStorage, 'getCookie') + .onFirstCall() + .returns(null) // .co.uk + .onSecondCall() + .returns('writeable'); // realdomain.co.uk; + }); + + afterEach(function () { + sandbox.restore(); + }); - it('should find the full domain when no subdomain is present', function () { - var domain = findRootDomain('realdomain.co.uk'); - expect(domain).to.be.eq('realdomain.co.uk'); + it('should just find the root domain', function () { + var domain = findRootDomain('sub.realdomain.co.uk'); + expect(domain).to.be.eq('realdomain.co.uk'); + }); + + it('should find the full domain when no subdomain is present', function () { + var domain = findRootDomain('realdomain.co.uk'); + expect(domain).to.be.eq('realdomain.co.uk'); + }); }); }); - }); + }) }); diff --git a/test/spec/modules/validationFpdModule_spec.js b/test/spec/modules/validationFpdModule_spec.js new file mode 100644 index 00000000000..b60360733d6 --- /dev/null +++ b/test/spec/modules/validationFpdModule_spec.js @@ -0,0 +1,395 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils.js'; +import { + filterArrayData, + validateFpd +} from 'modules/validationFpdModule/index.js'; + +describe('the first party data validation module', function () { + let ortb2 = { + device: { + h: 911, + w: 1733 + }, + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar', + ext: 'string' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + let conf = { + device: { + h: 500, + w: 750 + }, + user: { + keywords: 'test1, test2', + gender: 'f', + data: [{ + segment: [{ + id: 'test' + }], + name: 'alt' + }] + }, + site: { + ref: 'domain.com', + page: 'www.domain.com/test', + ext: { + data: { + inventory: ['first'] + } + } + } + }; + + describe('filtering first party array data', function () { + it('returns empty array if no valid data', function () { + let arr = [{}]; + let path = 'site.children.cat'; + let child = {type: 'string'}; + let parent = 'site'; + let key = 'cat'; + let validated = filterArrayData(arr, child, path, parent, key); + expect(validated).to.deep.equal([]); + }); + + it('filters invalid type of array data', function () { + let arr = ['foo', {test: 1}]; + let path = 'site.children.cat'; + let child = {type: 'string'}; + let parent = 'site'; + let key = 'cat'; + let validated = filterArrayData(arr, child, path, parent, key); + expect(validated).to.deep.equal(['foo']); + }); + + it('filters all data for missing required children', function () { + let arr = [{test: 1}]; + let path = 'site.children.content.children.data'; + let child = {type: 'object'}; + let parent = 'site'; + let key = 'data'; + let validated = filterArrayData(arr, child, path, parent, key); + expect(validated).to.deep.equal([]); + }); + + it('filters all data for invalid required children types', function () { + let arr = [{name: 'foo', segment: 1}]; + let path = 'site.children.content.children.data'; + let child = {type: 'object'}; + let parent = 'site'; + let key = 'data'; + let validated = filterArrayData(arr, child, path, parent, key); + expect(validated).to.deep.equal([]); + }); + + it('returns only data with valid required nested children types', function () { + let arr = [{name: 'foo', segment: [{id: '1'}, {id: 2}, 'foobar']}]; + let path = 'site.children.content.children.data'; + let child = {type: 'object'}; + let parent = 'site'; + let key = 'data'; + let validated = filterArrayData(arr, child, path, parent, key); + expect(validated).to.deep.equal([{name: 'foo', segment: [{id: '1'}]}]); + }); + }); + + describe('validating first party data', function () { + it('filters user.data[0].ext for incorrect type', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + let expected = { + device: { + h: 911, + w: 1733 + }, + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters user and site for empty data', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + let expected = { + device: { + h: 911, + w: 1733 + } + }; + + duplicate.user.data = []; + duplicate.site.content.data = []; + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters user for empty valid segment values', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + let expected = { + device: { + h: 911, + w: 1733 + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + duplicate.user.data[0].segment.push({test: 3}); + duplicate.user.data[0].segment[0] = {foo: 'bar'}; + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters user.data[0].ext and site.content.data[0].segement[1] for invalid data', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + let expected = { + device: { + h: 911, + w: 1733 + }, + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + duplicate.site.content.data[0].segment.push({test: 3}); + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters device for invalid data types', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + duplicate.device = { + h: '1', + w: '1' + } + + let expected = { + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + duplicate.site.content.data[0].segment.push({test: 3}); + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters cur for invalid data type', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + duplicate.cur = 8; + + let expected = { + device: { + h: 911, + w: 1733 + }, + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + duplicate.site.content.data[0].segment.push({test: 3}); + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters bcat, badv for invalid data type', function () { + const duplicate = utils.deepClone(ortb2); + duplicate.badv = 'adadadbcd.com'; + duplicate.bcat = ['IAB25', 'IAB7-39']; + + const expected = { + device: { + h: 911, + w: 1733 + }, + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + }, + bcat: ['IAB25', 'IAB7-39'] + }; + + const validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters site.publisher object properties for invalid data type', function () { + const duplicate = utils.deepClone(ortb2); + duplicate.site.publisher = { + id: '1', + domain: ['xyz.com'], + name: 'xyz', + }; + + const expected = { + device: { + h: 911, + w: 1733 + }, + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + }, + publisher: { + id: '1', + name: 'xyz', + } + } + }; + + const validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + }); +}); diff --git a/test/spec/modules/vdoaiBidAdapter_spec.js b/test/spec/modules/vdoaiBidAdapter_spec.js index d78a5ce04f6..b1cfa606d84 100644 --- a/test/spec/modules/vdoaiBidAdapter_spec.js +++ b/test/spec/modules/vdoaiBidAdapter_spec.js @@ -1,141 +1,146 @@ -import {assert, expect} from 'chai'; -import {spec} from 'modules/vdoaiBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; - -const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; - -describe('vdoaiBidAdapter', function () { - const adapter = newBidder(spec); - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'vdoai', - 'params': { - placementId: 'testPlacementId' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250] - ], - 'bidId': '1234asdf1234', - 'bidderRequestId': '1234asdf1234asdf', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' - }; - it('should return true where required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - }); - describe('buildRequests', function () { - let bidRequests = [ - { - 'bidder': 'vdoai', - 'params': { - placementId: 'testPlacementId' - }, - 'sizes': [ - [300, 250] - ], - 'bidId': '23beaa6af6cdde', - 'bidderRequestId': '19c0c1efdf37e7', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - 'mediaTypes': 'banner' - } - ]; - - let bidderRequests = { - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'https://example.com', - 'stack': ['https://example.com'] - } - }; - - const request = spec.buildRequests(bidRequests, bidderRequests); - it('sends bid request to our endpoint via POST', function () { - expect(request[0].method).to.equal('POST'); - }); - it('attaches source and version to endpoint URL as query params', function () { - expect(request[0].url).to.equal(ENDPOINT_URL); - }); - }); - - describe('interpretResponse', function () { - let bidRequest = [ - { - 'method': 'POST', - 'url': ENDPOINT_URL, - 'data': { - 'placementId': 'testPlacementId', - 'width': '300', - 'height': '200', - 'bidId': 'bidId123', - 'referer': 'www.example.com' - } - - } - ]; - let serverResponse = { - body: { - 'vdoCreative': '

I am an ad

', - 'price': 4.2, - 'adid': '12345asdfg', - 'currency': 'EUR', - 'statusMessage': 'Bid available', - 'requestId': 'bidId123', - 'width': 300, - 'height': 250, - 'netRevenue': true - } - }; - it('should get the correct bid response', function () { - let expectedResponse = [{ - 'requestId': 'bidId123', - 'cpm': 4.2, - 'width': 300, - 'height': 250, - 'creativeId': '12345asdfg', - 'currency': 'EUR', - 'netRevenue': true, - 'ttl': 3000, - 'ad': '

I am an ad

' - }]; - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); - }); - - it('handles instream video responses', function () { - let serverResponse = { - body: { - 'vdoCreative': '', - 'price': 4.2, - 'adid': '12345asdfg', - 'currency': 'EUR', - 'statusMessage': 'Bid available', - 'requestId': 'bidId123', - 'width': 300, - 'height': 250, - 'netRevenue': true, - 'mediaType': 'video' - } - }; - let bidRequest = [ - { - 'method': 'POST', - 'url': ENDPOINT_URL, - 'data': { - 'placementId': 'testPlacementId', - 'width': '300', - 'height': '200', - 'bidId': 'bidId123', - 'referer': 'www.example.com', - 'mediaType': 'video' - } - } - ]; - - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(result[0]).to.have.property('vastXml'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); - }); -}); +import {assert, expect} from 'chai'; +import {spec} from 'modules/vdoaiBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; + +describe('vdoaiBidAdapter', function () { + const adapter = newBidder(spec); + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'vdoai', + 'params': { + placementId: 'testPlacementId' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '1234asdf1234', + 'bidderRequestId': '1234asdf1234asdf', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' + }; + it('should return true where required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'vdoai', + 'params': { + placementId: 'testPlacementId' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + 'mediaTypes': 'banner' + } + ]; + + let bidderRequests = { + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'https://example.com', + 'stack': ['https://example.com'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequests); + it('sends bid request to our endpoint via POST', function () { + expect(request[0].method).to.equal('POST'); + }); + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.equal(ENDPOINT_URL); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = [ + { + 'method': 'POST', + 'url': ENDPOINT_URL, + 'data': { + 'placementId': 'testPlacementId', + 'width': '300', + 'height': '200', + 'bidId': 'bidId123', + 'referer': 'www.example.com' + } + + } + ]; + let serverResponse = { + body: { + 'vdoCreative': '

I am an ad

', + 'price': 4.2, + 'adid': '12345asdfg', + 'currency': 'EUR', + 'statusMessage': 'Bid available', + 'requestId': 'bidId123', + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'adDomain': ['text.abc'] + } + }; + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': 'bidId123', + 'cpm': 4.2, + 'width': 300, + 'height': 250, + 'creativeId': '12345asdfg', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 3000, + 'ad': '

I am an ad

', + 'meta': { + 'advertiserDomains': ['text.abc'] + } + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + expect(result[0].meta.advertiserDomains).to.deep.equal(expectedResponse[0].meta.advertiserDomains); + }); + + it('handles instream video responses', function () { + let serverResponse = { + body: { + 'vdoCreative': '', + 'price': 4.2, + 'adid': '12345asdfg', + 'currency': 'EUR', + 'statusMessage': 'Bid available', + 'requestId': 'bidId123', + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'mediaType': 'video' + } + }; + let bidRequest = [ + { + 'method': 'POST', + 'url': ENDPOINT_URL, + 'data': { + 'placementId': 'testPlacementId', + 'width': '300', + 'height': '200', + 'bidId': 'bidId123', + 'referer': 'www.example.com', + 'mediaType': 'video' + } + } + ]; + + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(result[0]).to.have.property('vastXml'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + }); +}); diff --git a/test/spec/modules/ventesBidAdapter_spec.js b/test/spec/modules/ventesBidAdapter_spec.js new file mode 100644 index 00000000000..219c24deced --- /dev/null +++ b/test/spec/modules/ventesBidAdapter_spec.js @@ -0,0 +1,845 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils.js'; +import { spec } from 'modules/ventesBidAdapter.js'; + +const BIDDER_URL = 'http://13.234.201.146:8088/va/ad'; + +describe('Ventes Adapter', function () { + const examples = { + adUnit_banner: { + adUnitCode: 'ad_unit_banner', + bidder: 'ventes', + bidderRequestId: 'bid_request_id', + bidId: 'bid_id', + params: { + publisherId: 'agltb3B1Yi1pbmNyDAsSA0FwcBiJkfTUCV', + placementId: 'VA-062-0013-0183', + device: { + ip: '123.145.167.189', + ifa: 'AEBE52E7-03EE-455A-B3C4-E57283966239', + } + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }, + + adUnitContext: { + refererInfo: { + referer: 'https://ventesavenues.in', + } + }, + + serverRequest_banner: { + method: 'POST', + url: 'http://13.234.201.146:8088/va/ad', + data: { + id: 'bid_request_id', + imp: [ + { + id: 'imp_id_banner', + banner: { + format: [{ + w: 300, + h: 200 + }] + } + } + ], + site: { + page: 'https://ventesavenues.in', + domain: 'ventesavenues.in', + name: 'ventesavenues.in' + }, + device: { + ua: '', + ip: '123.145.167.189', + ifa: 'AEBE52E7-03EE-455A-B3C4-E57283966239', + language: 'en' + }, + user: null, + regs: null, + at: 1 + } + }, + serverResponse_banner: { + body: { + cur: 'USD', + seatbid: [ + { + seat: '4', + bid: [ + { + id: 'id', + impid: 'imp_id_banner', + cid: 'campaign_id', + crid: 'creative_id', + adm: '..', + price: 1.5, + w: 300, + h: 200 + } + ] + } + ] + } + } + }; + + describe('isBidRequestValid', function () { + describe('General', function () { + it('should return false when not given an ad unit', function () { + const adUnit = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an invalid ad unit', function () { + const adUnit = 'bad_bid'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without bidder code', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidder = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a bad bidder code', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidder = 'unknownBidder'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without ad unit code', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.adUnitCode = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid ad unit code', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.adUnitCode = {}; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without bid request identifier', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidderRequestId = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid bid request identifier', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidderRequestId = {}; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without impression identifier', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidId = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid impression identifier', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.bidId = {}; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without media types', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with empty media types', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes = {}; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with invalid media types', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes = 'bad_media_types'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + }); + + describe('Banner', function () { + it('should return true when given a valid ad unit', function () { + const adUnit = examples.adUnit_banner; + + expect(spec.isBidRequestValid(adUnit)).to.equal(true); + }); + + it('should return true when given a valid ad unit with invalid publisher id', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.params = {}; + adUnit.params.publisherId = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return true when given a valid ad unit without placement id', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.params = {}; + adUnit.params.publisherId = 'agltb3B1Yi1pbmNyDAsSA0FwcBiJkfTUCV'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return true when given a valid ad unit with invalid placement id', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.params = {}; + adUnit.params.publisherId = 'agltb3B1Yi1pbmNyDAsSA0FwcBiJkfTUCV'; + adUnit.params.placementId = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit without size', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = undefined; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid size', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = 'bad_banner_size'; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an empty size', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = []; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid size value', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = ['bad_banner_size_value']; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a size value with less than 2 dimensions', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[300]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a size value with more than 2 dimensions', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[300, 250, 30]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a negative width value', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[-300, 250]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with a negative height value', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[300, -250]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid width value', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[false, 250]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + + it('should return false when given an ad unit with an invalid height value', function () { + const adUnit = utils.deepClone(examples.adUnit_banner); + adUnit.mediaTypes.banner.sizes = [[300, {}]]; + + expect(spec.isBidRequestValid(adUnit)).to.equal(false); + }); + }); + }); + + describe('buildRequests', function () { + describe('ServerRequest', function () { + it('should return a server request when given a valid ad unit and a valid ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + expect(serverRequests).to.be.an('array').and.to.have.length(1); + expect(serverRequests[0].method).to.exist.and.to.be.a('string').and.to.equal('POST'); + expect(serverRequests[0].url).to.exist.and.to.be.a('string').and.to.equal(BIDDER_URL); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + }); + + it('should return an empty server request list when given an empty ad unit list and a valid ad unit context', function () { + const adUnits = []; + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + expect(serverRequests).to.be.an('array').and.to.have.length(0); + }); + + it('should not return a server request when given no ad unit and a valid ad unit context', function () { + const serverRequests = spec.buildRequests(null, examples.adUnitContext); + expect(serverRequests).to.equal(null); + }); + + it('should not return a server request when given a valid ad unit and no ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + const serverRequests = spec.buildRequests(adUnits, null); + expect(serverRequests).to.be.an('array').and.to.have.length(1); + }); + + it('should not return a server request when given a valid ad unit and an invalid ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + const serverRequests = spec.buildRequests(adUnits, {}); + expect(serverRequests).to.be.an('array').and.to.have.length(1); + }); + }); + + describe('BidRequest', function () { + it('should return a valid server request when given a valid ad unit', function () { + const adUnits = [examples.adUnit_banner]; + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.at).to.exist.and.to.be.a('number').and.to.equal(1); + }); + + it('should return one server request when given one valid ad unit', function () { + const adUnits = [examples.adUnit_banner]; + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.id).to.exist.and.to.be.a('string').and.to.equal(adUnits[0].bidderRequestId); + }); + }); + + describe('Impression', function () { + describe('Banner', function () { + it('should return a server request with one impression when given a valid ad unit', function () { + const adUnits = [examples.adUnit_banner]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0]).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].id).to.exist.and.to.be.a('string').and.to.equal(`${adUnits[0].bidId}`); + expect(serverRequests[0].data.imp[0].banner).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.imp[0].banner.w).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][0]); + expect(serverRequests[0].data.imp[0].banner.h).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.banner.sizes[0][1]); + expect(serverRequests[0].data.imp[0].banner.format).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data.imp[0].banner.format[0]).to.exist.and.to.be.an('object').and.to.deep.equal({ + w: adUnits[0].mediaTypes.banner.sizes[0][0], + h: adUnits[0].mediaTypes.banner.sizes[0][1] + }); + }); + }); + }); + + describe('Site', function () { + it('should return a server request with site information when given a valid ad unit and a valid ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = examples.adUnitContext; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.site).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.site.page).to.exist.and.to.be.an('string').and.to.equal(adUnitContext.refererInfo.referer); + expect(serverRequests[0].data.site.domain).to.exist.and.to.be.an('string').and.to.equal('ventesavenues.in'); + expect(serverRequests[0].data.site.name).to.exist.and.to.be.an('string').and.to.equal('ventesavenues.in'); + }); + + it('should return a server request without site information when given an ad unit context without referer information', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.refererInfo = undefined; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + }); + + it('should return a server request without site information when given an ad unit context with invalid referer information', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.refererInfo = 'bad_referer_information'; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + }); + + it('should return a server request without site information when given an ad unit context without referer', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.refererInfo.referer = undefined; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + }); + + it('should return a server request without site information when given an ad unit context with an invalid referer', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.refererInfo.referer = {}; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + }); + + it('should return a server request without site information when given an ad unit context with a misformatted referer', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = utils.deepClone(examples.adUnitContext); + adUnitContext.refererInfo.referer = 'we-are-adot'; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + }); + }); + + describe('Device', function () { + it('should return a server request with device information when given a valid ad unit and a valid ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const serverRequests = spec.buildRequests(adUnits, examples.adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.device).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.device.ua).to.exist.and.to.be.a('string'); + expect(serverRequests[0].data.device.language).to.exist.and.to.be.a('string'); + }); + }); + + describe('User', function () { + it('should return a server request with user information when given a valid ad unit and a valid ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = examples.adUnitContext; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + expect(serverRequests[0].data.user).to.exist.and.to.be.an('object'); + }); + + it('should return a server request without user information when not given an ad unit context', function () { + const adUnits = [examples.adUnit_banner]; + + const adUnitContext = undefined; + + const serverRequests = spec.buildRequests(adUnits, adUnitContext); + + expect(serverRequests).to.be.an('array').and.to.have.lengthOf(1); + expect(serverRequests[0].data).to.exist.and.to.be.an('object'); + expect(serverRequests[0].data.id).to.exist.and.to.be.an('string').and.to.equal(adUnits[0].bidderRequestId); + }); + }); + }); + + describe('interpretResponse', function () { + describe('General', function () { + it('should return an ad when given a valid server response with one bid with USD currency', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.cur = 'USD'; + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); + }); + + it('should return no ad when not given a server response', function () { + const ads = spec.interpretResponse(null); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when not given a server response body', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given an invalid server response body', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body = 'invalid_body'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response without seat bids', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with invalid seat bids', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid = 'invalid_seat_bids'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with an empty seat bids array', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid = []; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with an invalid seat bid', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid = 'invalid_bids'; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with an empty bids array', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid = []; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with an invalid bid', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid = ['invalid_bid']; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without currency', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.cur = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid currency', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.cur = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without impression identifier', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].impid = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid impression identifier', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].impid = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without creative identifier', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].crid = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid creative identifier', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].crid = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without ad markup and ad serving URL', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].adm = undefined; + serverResponse.body.seatbid[0].bid[0].nurl = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid ad markup', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].adm = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without bid price', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].price = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid bid price', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].price = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and no server request', function () { + const serverRequest = undefined; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and an invalid server request', function () { + const serverRequest = 'bad_server_request'; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request without bid request', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest.data = undefined; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request with an invalid bid request', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest.data = 'bad_bid_request'; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request without impression', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest.data.imp = undefined; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a valid server response and a server request with an invalid impression field', function () { + const serverRequest = utils.deepClone(examples.serverRequest_banner); + serverRequest.data.imp = 'invalid_impressions'; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + }); + + describe('Banner', function () { + it('should return an ad when given a valid server response with one bid', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = examples.serverResponse_banner; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(1); + expect(ads[0].adUrl).to.equal(null); + expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); + expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); + expect(ads[0].netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(true); + expect(ads[0].ttl).to.exist.and.to.be.a('number').and.to.equal(10); + expect(ads[0].height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(ads[0].width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); + }); + + it('should return no ad when given a server response with a bid without height', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].h = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid height', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].h = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid without width', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].w = undefined; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + + it('should return no ad when given a server response with a bid with an invalid width', function () { + const serverRequest = examples.serverRequest_banner; + + const serverResponse = utils.deepClone(examples.serverResponse_banner); + serverResponse.body.seatbid[0].bid[0].w = {}; + + const ads = spec.interpretResponse(serverResponse, serverRequest); + + expect(ads).to.be.an('array').and.to.have.length(0); + }); + }); + }); +}); diff --git a/test/spec/modules/viBidAdapter_spec.js b/test/spec/modules/viBidAdapter_spec.js deleted file mode 100644 index e1a88c004bb..00000000000 --- a/test/spec/modules/viBidAdapter_spec.js +++ /dev/null @@ -1,911 +0,0 @@ -import { - ratioToPercentageCeil, - merge, - getDocumentHeight, - getOffset, - getWindowParents, - getRectCuts, - getTopmostReachableWindow, - topDocumentIsReachable, - isInsideIframe, - isInsideSafeframe, - getIframeType, - getFrameElements, - getElementCuts, - getInViewRatio, - getMayBecomeVisible, - getInViewPercentage, - getInViewRatioInsideTopFrame, - getOffsetTopDocument, - getOffsetTopDocumentPercentage, - getOffsetToView, - getOffsetToViewPercentage, - area, - get, - getViewabilityDescription, - mergeArrays, - documentFocus -} from 'modules/viBidAdapter.js'; - -describe('ratioToPercentageCeil', () => { - it('1 converts to percentage', () => - expect(ratioToPercentageCeil(0.01)).to.equal(1)); - it('2 converts to percentage', () => - expect(ratioToPercentageCeil(0.00000000001)).to.equal(1)); - it('3 converts to percentage', () => - expect(ratioToPercentageCeil(0.5)).to.equal(50)); - it('4 converts to percentage', () => - expect(ratioToPercentageCeil(1)).to.equal(100)); - it('5 converts to percentage', () => - expect(ratioToPercentageCeil(0.99)).to.equal(99)); - it('6 converts to percentage', () => - expect(ratioToPercentageCeil(0.990000000000001)).to.equal(100)); -}); - -describe('merge', () => { - it('merges two objects', () => { - expect( - merge({ a: 1, b: 2, d: 0 }, { a: 2, b: 2, c: 3 }, (a, b) => a + b) - ).to.deep.equal({ a: 3, b: 4, c: 3, d: 0 }); - }); -}); - -describe('getDocumentHeight', () => { - [ - { - curDocument: { - body: { - clientHeight: 0, - offsetHeight: 0, - scrollHeight: 0 - }, - documentElement: { - clientHeight: 0, - offsetHeight: 0, - scrollHeight: 0 - } - }, - expected: 0 - }, - { - curDocument: { - body: { - clientHeight: 0, - offsetHeight: 13, - scrollHeight: 24 - }, - documentElement: { - clientHeight: 0, - offsetHeight: 0, - scrollHeight: 0 - } - }, - expected: 24 - }, - { - curDocument: { - body: { - clientHeight: 0, - offsetHeight: 13, - scrollHeight: 24 - }, - documentElement: { - clientHeight: 100, - offsetHeight: 50, - scrollHeight: 30 - } - }, - expected: 100 - } - ].forEach(({ curDocument, expected }) => - expect(getDocumentHeight(curDocument)).to.be.equal(expected) - ); -}); - -describe('getOffset', () => { - [ - { - element: { - ownerDocument: { - defaultView: { - pageXOffset: 0, - pageYOffset: 0 - } - }, - getBoundingClientRect: () => ({ - top: 0, - right: 0, - bottom: 0, - left: 0 - }) - }, - expected: { - top: 0, - right: 0, - bottom: 0, - left: 0 - } - } - ].forEach(({ description, element, expected }, i) => - it( - 'returns element offsets from the document edges (including scroll): ' + - i, - () => expect(getOffset(element)).to.be.deep.equal(expected) - ) - ); - it('Throws when there is no window', () => - expect( - getOffset.bind(null, { - ownerDocument: { - defaultView: null - }, - getBoundingClientRect: () => ({ - top: 0, - right: 0, - bottom: 0, - left: 0 - }) - }) - ).to.throw()); -}); - -describe('getWindowParents', () => { - const win = {}; - win.top = win; - win.parent = win; - const win1 = { top: win, parent: win }; - const win2 = { top: win, parent: win1 }; - const win3 = { top: win, parent: win2 }; - - it('get parents up to the top', () => - expect(getWindowParents(win3)).to.be.deep.equal([win2, win1, win])); -}); - -describe('getTopmostReachableWindow', () => { - const win = {}; - win.top = win; - win.parent = win; - const win1 = { top: win, parent: win }; - const win2 = { top: win, parent: win1 }; - const win3 = { top: win, parent: win2 }; - - it('get parents up to the top', () => - expect(getTopmostReachableWindow(win3)).to.be.equal(win)); -}); - -const topWindow = { document, frameElement: 0 }; -topWindow.top = topWindow; -topWindow.parent = topWindow; -const topFrameElement = { - ownerDocument: { - defaultView: topWindow - } -}; -const frameWindow1 = { - top: topWindow, - parent: topWindow, - frameElement: topFrameElement -}; -const frameElement1 = { - ownerDocument: { - defaultView: frameWindow1 - } -}; -const frameWindow2 = { - top: topWindow, - parent: frameWindow1, - frameElement: frameElement1 -}; -const frameElement2 = { - ownerDocument: { - defaultView: frameWindow2 - } -}; -const frameWindow3 = { - top: topWindow, - parent: frameWindow2, - frameElement: frameElement2 -}; - -describe('topDocumentIsReachable', () => { - it('returns true if it no inside iframe', () => - expect(topDocumentIsReachable(topWindow)).to.be.true); - it('returns true if it can access top document', () => - expect(topDocumentIsReachable(frameWindow3)).to.be.true); -}); - -describe('isInsideIframe', () => { - it('returns true if window !== window.top', () => - expect(isInsideIframe(topWindow)).to.be.false); - it('returns true if window !== window.top', () => - expect(isInsideIframe(frameWindow1)).to.be.true); -}); - -const safeframeWindow = { $sf: {} }; - -describe('isInsideSafeframe', () => { - it('returns true if top window is not reachable and window.$sf is defined', () => - expect(isInsideSafeframe(safeframeWindow)).to.be.true); -}); - -const hostileFrameWindow = {}; - -describe('getIframeType', () => { - it('returns undefined when is not inside iframe', () => - expect(getIframeType(topWindow)).to.be.undefined); - it("returns 'safeframe' when inside sf", () => - expect(getIframeType(safeframeWindow)).to.be.equal('safeframe')); - it("returns 'friendly' when inside friendly iframe and can reach top window", () => - expect(getIframeType(frameWindow3)).to.be.equal('friendly')); - it("returns 'nonfriendly' when cannot get top window", () => - expect(getIframeType(hostileFrameWindow)).to.be.equal('nonfriendly')); -}); - -describe('getFrameElements', () => { - it('it returns a list iframe elements up to the top, topmost goes first', () => { - expect(getFrameElements(frameWindow3)).to.be.deep.equal([ - topFrameElement, - frameElement1, - frameElement2 - ]); - }); -}); - -describe('area', () => { - it('calculates area', () => expect(area(10, 10)).to.be.equal(100)); - it('calculates area', () => - expect( - area(10, 10, { top: -2, left: -2, bottom: 0, right: 0 }) - ).to.be.equal(64)); -}); - -describe('getElementCuts', () => { - it('returns element cuts', () => - expect( - getElementCuts({ - getBoundingClientRect() { - return { - top: 0, - right: 200, - bottom: 200, - left: 0 - }; - }, - ownerDocument: { - defaultView: { - innerHeight: 1000, - innerWidth: 1000 - } - } - }) - ).to.be.deep.equal({ - top: 0, - right: 0, - bottom: 0, - left: 0 - })); -}); - -describe('getInViewRatio', () => { - it('returns inViewRatio', () => - expect( - getInViewRatio({ - ownerDocument: { - defaultView: { - innerHeight: 1000, - innerWidth: 1000 - } - }, - offsetWidth: 200, - offsetHeight: 200, - getBoundingClientRect() { - return { - top: 0, - right: 200, - bottom: 200, - left: 0 - }; - } - }) - ).to.be.deep.equal(1)); -}); - -describe('getMayBecomeVisible', () => { - it('returns true if not inside iframe of visible inside the iframe', () => - expect( - getMayBecomeVisible({ - ownerDocument: { - defaultView: { - innerHeight: 1000, - innerWidth: 1000 - } - }, - offsetWidth: 200, - offsetHeight: 200, - getBoundingClientRect() { - return { - top: 0, - right: 200, - bottom: 200, - left: 0 - }; - } - }) - ).to.be.true); -}); - -describe('getInViewPercentage', () => { - it('returns inViewRatioPercentage', () => - expect( - getInViewPercentage({ - ownerDocument: { - defaultView: { - innerHeight: 1000, - innerWidth: 1000 - } - }, - offsetWidth: 200, - offsetHeight: 200, - getBoundingClientRect() { - return { - top: 0, - right: 200, - bottom: 200, - left: 0 - }; - } - }) - ).to.be.deep.equal(100)); -}); - -describe('getInViewRatioInsideTopFrame', () => { - it('returns inViewRatio', () => - expect( - getInViewRatioInsideTopFrame({ - ownerDocument: { - defaultView: { - innerHeight: 1000, - innerWidth: 1000 - } - }, - offsetWidth: 200, - offsetHeight: 200, - getBoundingClientRect() { - return { - top: 0, - right: 200, - bottom: 200, - left: 0 - }; - } - }) - ).to.be.deep.equal(1)); -}); - -describe('getOffsetTopDocument', () => { - it('returns offset relative to the top document', () => - expect( - getOffsetTopDocument({ - ownerDocument: { - defaultView: { - pageXOffset: 0, - pageYOffset: 0 - } - }, - getBoundingClientRect: () => ({ - top: 0, - right: 0, - bottom: 0, - left: 0 - }) - }) - ).to.be.deep.equal({ - top: 0, - right: 0, - bottom: 0, - left: 0 - })); -}); - -describe('getOffsetTopDocumentPercentage', () => { - it('returns offset from the top as a percentage of the page length', () => { - const topWindow = { - pageXOffset: 0, - pageYOffset: 100, - document: { - body: { - clientHeight: 1000 - } - } - }; - topWindow.top = topWindow; - topWindow.parent = topWindow; - expect( - getOffsetTopDocumentPercentage({ - ownerDocument: { - defaultView: topWindow - }, - getBoundingClientRect: () => ({ - top: 100, - right: 0, - bottom: 0, - left: 0 - }) - }) - ).to.be.equal(20); - }); - it('throws when cannot get window', () => - expect(() => - getOffsetTopDocumentPercentage({ - ownerDocument: {} - }) - ).to.throw()); - it("throw when top document isn't reachable", () => { - const topWindow = { ...topWindow, document: null }; - expect(() => - getOffsetTopDocumentPercentage({ - ownerDocument: { - defaultView: { - top: topWindow - } - } - }) - ).to.throw(); - }); -}); - -describe('getOffsetToView', () => { - expect( - getOffsetToView({ - ownerDocument: { - defaultView: { - scrollY: 0, - pageXOffset: 0, - pageYOffset: 0 - } - }, - getBoundingClientRect: () => ({ - top: 0, - right: 0, - bottom: 0, - left: 0 - }) - }) - ).to.be.equal(0); -}); - -describe('getOffsetToView', () => { - expect( - getOffsetToViewPercentage({ - ownerDocument: { - defaultView: { - scrollY: 0, - pageXOffset: 0, - pageYOffset: 0, - document: { - body: { - clientHeight: 1000 - } - } - } - }, - getBoundingClientRect: () => ({ - top: 0, - right: 0, - bottom: 0, - left: 0 - }) - }) - ).to.be.equal(0); -}); - -describe('getCuts without vCuts', () => { - const cases = { - 'completely in view 1': { - top: 0, - bottom: 200, - right: 200, - left: 0, - vw: 300, - vh: 300, - expected: { - top: 0, - right: 0, - bottom: 0, - left: 0 - } - }, - 'completely in view 2': { - top: 100, - bottom: 200, - right: 200, - left: 0, - vw: 300, - vh: 300, - expected: { - top: 0, - right: 0, - bottom: 0, - left: 0 - } - }, - 'half cut from the top': { - top: -200, - bottom: 200, - right: 200, - left: 0, - vw: 300, - vh: 300, - expected: { - top: -200, - right: 0, - bottom: 0, - left: 0 - } - }, - 'half cut from the bottom': { - top: 0, - bottom: 600, - right: 200, - left: 0, - vw: 300, - vh: 300, - expected: { - top: 0, - right: 0, - bottom: -300, - left: 0 - } - }, - 'quarter cut from top and bottom': { - top: -25, - bottom: 75, - right: 200, - left: 0, - vw: 300, - vh: 50, - expected: { - top: -25, - right: 0, - bottom: -25, - left: 0 - } - }, - 'out of view top': { - top: -200, - bottom: -5, - right: 200, - left: 0, - vw: 300, - vh: 200, - expected: { - top: -200, - right: 0, - bottom: 0, - left: 0 - } - }, - 'out of view bottom': { - top: 250, - bottom: 500, - right: 200, - left: 0, - vw: 300, - vh: 200, - expected: { - top: 0, - right: 0, - bottom: -300, - left: 0 - } - }, - 'half cut from left': { - top: 0, - bottom: 200, - left: -200, - right: 200, - vw: 300, - vh: 300, - expected: { - top: 0, - right: 0, - bottom: 0, - left: -200 - } - }, - 'half cut from left and top': { - top: -100, - bottom: 100, - left: -200, - right: 200, - vw: 300, - vh: 300, - expected: { - top: -100, - right: 0, - bottom: 0, - left: -200 - } - }, - 'quarter cut from all sides': { - top: -100, - left: -100, - bottom: 300, - right: 300, - vw: 200, - vh: 200, - expected: { - top: -100, - right: -100, - bottom: -100, - left: -100 - } - } - }; - for (let descr in cases) { - it(descr, () => { - const { expected, vh, vw, ...rect } = cases[descr]; - expect(getRectCuts(rect, vh, vw)).to.deep.equal(expected); - }); - } -}); - -describe('getCuts with vCuts', () => { - const cases = { - 'completely in view 1, half-cut viewport from top': { - top: 0, - right: 200, - bottom: 200, - left: 0, - vw: 200, - vh: 200, - vCuts: { - top: -100, - right: 0, - bottom: 0, - left: 0 - }, - expected: { - top: -100, - right: 0, - bottom: 0, - left: 0 - } - }, - 'completely in view 2, half-cut viewport from bottom': { - top: 100, - bottom: 200, - right: 200, - left: 0, - vw: 300, - vh: 300, - vCuts: { - top: 0, - right: 0, - bottom: -150, - left: 0 - }, - expected: { - top: 0, - right: 0, - bottom: -50, - left: 0 - } - }, - 'half cut from the top, 1/3 viewport cut from the bottom': { - top: -200, - bottom: 200, - right: 200, - left: 0, - vw: 300, - vh: 300, - vCuts: { - top: 0, - right: 0, - bottom: -100, - left: 0 - }, - expected: { - top: -200, - right: 0, - bottom: 0, - left: 0 - } - }, - 'half cut from the bottom': { - top: 0, - bottom: 600, - right: 200, - left: 0, - vw: 300, - vh: 300, - expected: { - top: 0, - right: 0, - bottom: -300, - left: 0 - } - }, - 'quarter cut from top and bottom': { - top: -25, - bottom: 75, - right: 200, - left: 0, - vw: 300, - vh: 50, - expected: { - top: -25, - right: 0, - bottom: -25, - left: 0 - } - }, - 'out of view top': { - top: -200, - bottom: -5, - right: 200, - left: 0, - vw: 300, - vh: 200, - expected: { - top: -200, - right: 0, - bottom: 0, - left: 0 - } - }, - 'out of view bottom': { - top: 250, - bottom: 500, - right: 200, - left: 0, - vw: 300, - vh: 200, - expected: { - top: 0, - right: 0, - bottom: -300, - left: 0 - } - }, - 'half cut from left': { - top: 0, - bottom: 200, - left: -200, - right: 200, - vw: 300, - vh: 300, - expected: { - top: 0, - right: 0, - bottom: 0, - left: -200 - } - }, - 'half cut from left and top': { - top: -100, - bottom: 100, - left: -200, - right: 200, - vw: 300, - vh: 300, - expected: { - top: -100, - right: 0, - bottom: 0, - left: -200 - } - }, - 'quarter cut from all sides': { - top: -100, - left: -100, - bottom: 300, - right: 300, - vw: 200, - vh: 200, - expected: { - top: -100, - right: -100, - bottom: -100, - left: -100 - } - } - }; - for (let descr in cases) { - it(descr, () => { - const { expected, vh, vw, vCuts, ...rect } = cases[descr]; - expect(getRectCuts(rect, vh, vw, vCuts)).to.deep.equal(expected); - }); - } -}); - -describe('get', () => { - it('returns a property in a nested object 1', () => - expect(get(['a'], { a: 1 })).to.equal(1)); - it('returns a property in a nested object 2', () => - expect(get(['a', 'b'], { a: { b: 1 } })).to.equal(1)); - it('returns a property in a nested object 3', () => - expect(get(['a', 'b'], { a: { b: 1 } })).to.equal(1)); - it('returns undefined if property does not exist', () => - expect(get(['a', 'b'], { b: 1 })).to.equal(undefined)); - it('returns undefined if property does not exist', () => - expect(get(['a', 'b'], undefined)).to.equal(undefined)); - it('returns undefined if property does not exist', () => - expect(get(['a', 'b'], 1213)).to.equal(undefined)); - const DEFAULT = -5; - it('returns defaultValue if property does not exist', () => - expect(get(['a', 'b'], { b: 1 }, DEFAULT)).to.equal(DEFAULT)); - it('returns defaultValue if property does not exist', () => - expect(get(['a', 'b'], undefined, DEFAULT)).to.equal(DEFAULT)); - it('returns defaultValue if property does not exist', () => - expect(get(['a', 'b'], 1213, DEFAULT)).to.equal(DEFAULT)); - it('can work with arrays 1', () => expect(get([0, 1], [[1, 2]])).to.equal(2)); - it('can work with arrays 2', () => - expect(get([0, 'a'], [{ a: 42 }])).to.equal(42)); -}); - -describe('getViewabilityDescription', () => { - it('returns error when there is no element', () => { - expect(getViewabilityDescription(null)).to.deep.equal({ - error: 'no element' - }); - }); - it('returns only iframe type for nonfrienly iframe', () => { - expect( - getViewabilityDescription({ - ownerDocument: { - defaultView: {} - } - }) - ).to.deep.equal({ - iframeType: 'nonfriendly' - }); - }); - it('returns only iframe type for safeframe iframe', () => { - expect( - getViewabilityDescription({ - ownerDocument: { - defaultView: { - $sf: true - } - } - }) - ).to.deep.equal({ - iframeType: 'safeframe' - }); - }); -}); - -describe('mergeSizes', () => { - it('merges provides arrays of tuples, leaving only unique', () => { - expect( - mergeArrays(x => x.join(','), [[1, 2], [2, 4]], [[1, 2]]) - ).to.deep.equal([[1, 2], [2, 4]]); - }); - it('merges provides arrays of tuples, leaving only unique', () => { - expect( - mergeArrays( - x => x.join(','), - [[1, 2], [2, 4]], - [[1, 2]], - [[400, 500], [500, 600]] - ) - ).to.deep.equal([[1, 2], [2, 4], [400, 500], [500, 600]]); - }); -}); - -describe('documentFocus', () => { - it('calls hasFocus function if it present, converting boolean to an int 0/1 value, returns undefined otherwise', () => { - expect( - documentFocus({ - hasFocus: () => true - }) - ).to.equal(1); - expect( - documentFocus({ - hasFocus: () => false - }) - ).to.equal(0); - expect(documentFocus({})).to.be.undefined; - }); -}); diff --git a/test/spec/modules/vibrantmediaBidAdapter_spec.js b/test/spec/modules/vibrantmediaBidAdapter_spec.js new file mode 100644 index 00000000000..c6ce7d52fb3 --- /dev/null +++ b/test/spec/modules/vibrantmediaBidAdapter_spec.js @@ -0,0 +1,1237 @@ +import {expect} from 'chai'; +import {spec} from 'modules/vibrantmediaBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from 'src/mediaTypes.js'; +import {INSTREAM, OUTSTREAM} from 'src/video.js'; + +const EXPECTED_PREBID_SERVER_URL = 'https://prebid.intellitxt.com/prebid'; + +const BANNER_AD = + 'Test Banner Ad UnitHello!'; +const VIDEO_AD = 'Test Video Ad Unit' + + ''; + +const VALID_BANNER_BID_PARAMS = Object.freeze({ + member: '1234', + invCode: 'ABCD', + placementId: '10433394' +}); + +const VALID_VIDEO_BID_PARAMS = Object.freeze({ + member: '1234', + invCode: 'ABCD', + placementId: '10433394', + video: { + skippable: false, + playback_method: 'auto_play_sound_off' + } +}); + +const VALID_NATIVE_BID_PARAMS = VALID_BANNER_BID_PARAMS; + +const DEFAULT_BID_SIZES = [[300, 250], [600, 240]]; + +const VALID_CONSENT_STRING = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + +const getValidBidderRequest = (bidRequests) => { + return Object.freeze({ + bidderCode: 'vibrantmedia', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: VALID_CONSENT_STRING, + vendorData: {}, + gdprApplies: true, + }, + bids: bidRequests, + }); +}; + +describe('VibrantMediaBidAdapter', function () { + const adapter = newBidder(spec); + + describe('constants', function () { + expect(spec.code).to.equal('vibrantmedia'); + expect(spec.supportedMediaTypes).to.deep.equal([BANNER, NATIVE, VIDEO]); + }); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('transformBidParams', function () { + it('transforms bid params correctly', function () { + expect(spec.transformBidParams(VALID_VIDEO_BID_PARAMS)).to.deep.equal(VALID_VIDEO_BID_PARAMS); + }); + }) + + let bidRequest; + + beforeEach(function () { + bidRequest = { + bidder: 'vibrantmedia', + params: { + // Filled in by individual tests + }, + mediaTypes: { + // Filled in by individual tests + }, + adUnitCode: 'test-div', + transactionId: '13579acef87623', + placementId: '7623587623857', + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }; + }); + + describe('isBidRequestValid', function () { + describe('with banner bid requests', function () { + beforeEach(function () { + bidRequest.mediaTypes.banner = { + sizes: DEFAULT_BID_SIZES, + }; + }); + + it('should return true for a valid banner bid request', function () { + bidRequest.params = VALID_BANNER_BID_PARAMS; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid banner bid request with a member id and inventory code', function () { + bidRequest.params = { + member: '1234', + invCode: 'ABCD', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid banner bid request with a placement id', function () { + bidRequest.params = { + placementId: '10433394', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for a valid banner bid request but with a member id and no inventory code', function () { + bidRequest.params = { + member: '1234', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid banner bid request but with no member id and an inventory code', function () { + bidRequest.params = { + invCode: 'ABCD', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid banner bid request but with no supported media types', function () { + bidRequest.params = { + placementId: '10433394', + }; + delete bidRequest.mediaTypes.banner; + bidRequest.mediaTypes.unsupported = { + sizes: DEFAULT_BID_SIZES, + } + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid banner bid request but with no params', function () { + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('with video bid requests', function () { + describe('with sizes attribute', function () { + const validVideoMediaTypes = { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + minduration: 1, + maxduration: 60, + skip: 0, + skipafter: 5, + playbackmethod: [2], + protocols: [1, 2, 3] + }; + + it('should return true for a valid video bid request', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for an instream video bid request', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + context: INSTREAM, + sizes: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a video bid request with an unknown context', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + context: 'fake', + sizes: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a video bid request with no context', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + sizes: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return true for a valid video bid request with a member id and inventory code', function () { + bidRequest.params = { + member: '1234', + invCode: 'ABCD', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid video bid request with a placement id', function () { + bidRequest.params = { + placementId: '10433394', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for a valid video bid request but with a member id and no inventory code', function () { + bidRequest.params = { + member: '1234', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid video bid request but with no member id and an inventory code', function () { + bidRequest.params = { + invCode: 'ABCD', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid video bid request but with no params', function () { + bidRequest.params = {}; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('with playerSize attribute', function () { + const validVideoMediaTypes = { + context: OUTSTREAM, + playerSize: DEFAULT_BID_SIZES, + minduration: 1, + maxduration: 60, + skip: 0, + skipafter: 5, + playbackmethod: [2], + protocols: [1, 2, 3] + }; + + beforeEach(function () { + bidRequest.mediaTypes.video = { + // Filled in by individual tests + }; + }); + + it('should return true for a valid video bid request', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for an instream video bid request', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + context: INSTREAM, + playerSize: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a video bid request with an unknown context', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + context: 'fake', + playerSize: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a video bid request with no context', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + playerSize: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return true for a valid video bid request with a member id and inventory code', function () { + bidRequest.params = { + member: '1234', + invCode: 'ABCD', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid video bid request with a placement id', function () { + bidRequest.params = { + placementId: '10433394', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for a valid video bid request but with a member id and no inventory code', function () { + bidRequest.params = { + member: '1234', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid video bid request but with no member id and an inventory code', function () { + bidRequest.params = { + invCode: 'ABCD', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid video bid request but with no params', function () { + bidRequest.params = {}; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + }); + + describe('with native bid requests', function () { + beforeEach(function () { + bidRequest.mediaTypes.native = { + image: { + required: true, + // Sizes is filled in by individual tests + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + } + }; + }); + + it('should return true for a valid native bid request with a single size', function () { + bidRequest.params = VALID_NATIVE_BID_PARAMS; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid native bid request with multiple sizes', function () { + bidRequest.params = VALID_NATIVE_BID_PARAMS; + bidRequest.mediaTypes.native.image.sizes = [[300, 250], [300, 600]]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid native bid request with a member id and inventory code', function () { + bidRequest.params = { + member: '1234', + invCode: 'ABCD', + }; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid native bid request with a placement id', function () { + bidRequest.params = { + placementId: '10433394', + }; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for a valid native bid request but with a member id and no inventory code', function () { + bidRequest.params = { + member: '1234', + }; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid native bid request but with no member id and an inventory code', function () { + bidRequest.params = { + invCode: 'ABCD', + }; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid native bid request but with no params', function () { + bidRequest.params = {}; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a native bid request with no image property', function () { + bidRequest.params = VALID_NATIVE_BID_PARAMS; + delete bidRequest.mediaTypes.native.image; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + }); + + describe('buildRequests', function () { + let bidRequests; + + beforeEach(function () { + bidRequests = [bidRequest]; + + bidRequests[0].params = VALID_BANNER_BID_PARAMS; + bidRequests[0].mediaTypes.banner = { + sizes: DEFAULT_BID_SIZES, + }; + }); + + it('should use HTTP POST', function () { + const request = spec.buildRequests(bidRequests, {}); + expect(request.method).to.equal('POST'); + }); + + it('should use the correct prebid server URL', function () { + const request = spec.buildRequests(bidRequests, {}); + expect(request.url).to.equal(EXPECTED_PREBID_SERVER_URL); + }); + + it('should add the page URL to the server request', function () { + const request = spec.buildRequests(bidRequests, {}); + const payload = JSON.parse(request.data); + + expect(payload.url).to.exist; + expect(payload.url).to.be.a('string'); + }); + + it('should add GDPR consent to the server request, where present', function () { + const bidderRequest = { + bidderCode: 'vibrantmedia', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: VALID_CONSENT_STRING, + gdprApplies: true, + }, + bids: bidRequests, + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + // TODO: Check that we should not be implementing withCredentials + // expect(request.options).to.deep.equal({withCredentials: true}); + + const payload = JSON.parse(request.data); + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consentString).to.exist.and.to.equal(VALID_CONSENT_STRING); + expect(payload.gdpr.gdprApplies).to.exist.and.to.be.true; + }); + + it('should add USP consent to the server request, where present', function () { + const bidderRequest = { + bidderCode: 'vibrantmedia', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + uspConsent: { + cmpApi: 'iab', + timeout: 10000, + consentData: { + testDatum: true + } + }, + bids: bidRequests + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + // TODO: Check that we should not be implementing withCredentials + // expect(request.options).to.deep.equal({withCredentials: true}); + + const payload = JSON.parse(request.data); + expect(payload.usp).to.exist; + expect(payload.usp.cmpApi).to.exist.and.to.equal('iab'); + expect(payload.usp.timeout).to.exist.and.to.equal(10000); + expect(payload.usp.consentData).to.exist.and.to.deep.equal({ + testDatum: true + }); + }); + + it('should add GDPR and USP consent to the server request, where both present', function () { + const bidderRequest = { + bidderCode: 'vibrantmedia', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: VALID_CONSENT_STRING, + gdprApplies: true, + }, + uspConsent: { + cmpApi: 'iab', + timeout: 10000, + consentData: { + testDatum: true + } + }, + bids: bidRequests, + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + // TODO: Check that we should not be implementing withCredentials + // expect(request.options).to.deep.equal({withCredentials: true}); + + const payload = JSON.parse(request.data); + + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consentString).to.exist.and.to.equal(VALID_CONSENT_STRING); + expect(payload.gdpr.gdprApplies).to.exist.and.to.be.true; + + expect(payload.usp).to.exist; + expect(payload.usp.cmpApi).to.exist.and.to.equal('iab'); + expect(payload.usp.timeout).to.exist.and.to.equal(10000); + expect(payload.usp.consentData).to.exist.and.to.deep.equal({ + testDatum: true + }); + }); + + it('should add window dimensions to the server request', function () { + const request = spec.buildRequests(bidRequests, {}); + const payload = JSON.parse(request.data); + + expect(payload.window).to.exist; + expect(payload.window.width).to.equal(window.innerWidth); + expect(payload.window.height).to.equal(window.innerHeight); + }); + + it('should add the top-level sizes to the bid request, if present', function () { + bidRequest.params = VALID_BANNER_BID_PARAMS; + bidRequest.sizes = DEFAULT_BID_SIZES; + bidRequest.mediaTypes = { + banner: {}, + }; + + const request = spec.buildRequests(bidRequests, {}, true); + const payload = JSON.parse(request.data); + + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(1); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].sizes).to.deep.equal(DEFAULT_BID_SIZES); + expect(payload.biddata[0].mediaTypes).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({}); + }); + + it('should add the list of bids to the bid request, if present', function () { + const testBid = { + bidder: 'testBidder', + params: { + placement: '12345' + } + }; + + bidRequest.params = VALID_BANNER_BID_PARAMS; + bidRequest.bids = [testBid]; + bidRequest.mediaTypes = { + banner: {}, + }; + + // These will be present in the list of bids instead + delete bidRequest.bidId; + delete bidRequest.transactionId; + delete bidRequest.bidder; + + const request = spec.buildRequests(bidRequests, {}, true); + const payload = JSON.parse(request.data); + + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(1); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].bids.length).to.equal(1); + expect(payload.biddata[0].bids[0]).to.deep.equal(testBid); + expect(payload.biddata[0].mediaTypes).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({}); + }); + + it('should add the correct bid data to the server request for one bid request', function () { + bidRequest.params = VALID_BANNER_BID_PARAMS; + bidRequest.mediaTypes = { + banner: { + sizes: DEFAULT_BID_SIZES, + }, + }; + + const request = spec.buildRequests(bidRequests, {}, true); + const payload = JSON.parse(request.data); + + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(1); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].sizes).to.be.undefined; + expect(payload.biddata[0].mediaTypes).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + }); + + it('should add the correct bid data to the server request for multiple bid requests', function () { + bidRequest.params = VALID_BANNER_BID_PARAMS; + bidRequest.mediaTypes = { + banner: { + sizes: DEFAULT_BID_SIZES, + }, + }; + const bid2 = { + bidder: 'vibrantmedia', + params: VALID_VIDEO_BID_PARAMS, + mediaTypes: { + video: { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + } + }, + adUnitCode: 'video-div', + bidId: '30b31c1838de1f', + placementId: '135797531abcdef', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }; + const bid3 = { + bidder: 'vibrantmedia', + params: VALID_NATIVE_BID_PARAMS, + mediaTypes: { + native: { + image: { + required: true, + sizes: [300, 250] + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + } + } + }, + adUnitCode: 'native-div', + bidId: '30b31c1838de14', + placementId: '918273645abcdef', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }; + + bidRequests.push(bid2, bid3); + + const request = spec.buildRequests(bidRequests, {}, true); + const payload = JSON.parse(request.data); + + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(3); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].mediaTypes).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[1]).to.exist; + expect(payload.biddata[1].code).to.equal(bid2.adUnitCode); + expect(payload.biddata[1].id).to.equal(bid2.placementId); + expect(payload.biddata[1].bidder).to.equal(bid2.bidder); + expect(payload.biddata[1].mediaTypes).to.exist; + expect(payload.biddata[1].mediaTypes[VIDEO]).to.exist; + expect(payload.biddata[1].mediaTypes[VIDEO]).to.deep.equal({ + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[2]).to.exist; + expect(payload.biddata[2].code).to.equal(bid3.adUnitCode); + expect(payload.biddata[2].id).to.equal(bid3.placementId); + expect(payload.biddata[2].bidder).to.equal(bid3.bidder); + expect(payload.biddata[2].mediaTypes[NATIVE]).to.exist; + expect(payload.biddata[2].mediaTypes[NATIVE]).to.deep.equal(bid3.mediaTypes.native); + }); + + it('should add the correct bid data to the bid request where a bid has multiple media types', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes = { + banner: { + sizes: DEFAULT_BID_SIZES, + }, + video: { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }, + native: { + image: { + required: true, + sizes: [300, 250] + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + } + } + }; + bidRequest.adUnitCode = 'mixed-div'; + + const bid2 = { + bidder: 'vibrantmedia', + params: VALID_VIDEO_BID_PARAMS, + mediaTypes: { + video: { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + } + }, + adUnitCode: 'video-div', + bidId: '30b31c1838de1a', + bidderRequestId: '22edbae2733bf6', + placementId: '293857832abfef', + auctionId: '1d1a030790a475', + }; + + bidRequests.push(bid2); + + const request = spec.buildRequests(bidRequests, {}, true); + const payload = JSON.parse(request.data); + + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(2); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].mediaTypes).to.exist; + expect(Object.keys(payload.biddata[0].mediaTypes).length).to.equal(3); + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[0].mediaTypes[VIDEO]).to.exist; + expect(payload.biddata[0].mediaTypes[VIDEO]).to.deep.equal({ + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].mediaTypes[NATIVE]).to.exist; + expect(payload.biddata[0].mediaTypes[NATIVE]).to.deep.equal(bidRequest.mediaTypes.native); + expect(payload.biddata[1]).to.exist; + expect(payload.biddata[1].code).to.equal(bid2.adUnitCode); + expect(payload.biddata[1].id).to.equal(bid2.placementId); + expect(payload.biddata[1].bidder).to.equal(bid2.bidder); + expect(payload.biddata[1].mediaTypes).to.exist; + expect(Object.keys(payload.biddata[1].mediaTypes).length).to.equal(1); + expect(payload.biddata[1].mediaTypes[VIDEO]).to.exist; + expect(payload.biddata[1].mediaTypes[VIDEO]).to.deep.equal({ + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }); + }); + }); + + describe('interpretResponse', function () { + it('returns a valid Prebid API response object for a banner Prebid Server response', function () { + const prebidServerResponse = { + body: [{ + mediaType: 'banner', + requestId: '12345', + cpm: 1, + currency: 'USD', + width: 640, + height: 240, + ad: BANNER_AD, + ttl: 300, + creativeId: '86f4aef9-2f17-421d-84db-5c9814bf4a79', + netRevenue: false, + meta: Object.freeze({ + advertiser: '105600', + width: 300, + height: 250, + isCustom: '1', + progressBar: false, + mpuSrc: '//images.intellitxt.com/a/105600/Genpact/genpact.jpg', + clickURL: '{{click}}' + }) + }] + }; + + const interpretedResponse = spec.interpretResponse(prebidServerResponse, {}); + + expect(interpretedResponse).to.be.a('array'); + expect(interpretedResponse.length).to.equal(1); + + const interpretedBid = interpretedResponse[0]; + + expect(interpretedBid.mediaType).to.equal('banner'); + expect(interpretedBid.requestId).to.equal('12345'); + expect(interpretedBid.cpm).to.equal(1); + expect(interpretedBid.currency).to.equal('USD'); + expect(interpretedBid.width).to.equal(640); + expect(interpretedBid.height).to.equal(240); + expect(interpretedBid.ad).to.equal(BANNER_AD); + expect(interpretedBid.ttl).to.equal(300); + expect(interpretedBid.creativeId).to.equal('86f4aef9-2f17-421d-84db-5c9814bf4a79'); + expect(interpretedBid.netRevenue).to.be.false; + expect(interpretedBid.meta).to.deep.equal(prebidServerResponse.body[0].meta); + expect(interpretedBid.renderer).to.be.undefined; + expect(interpretedBid.adResponse).to.deep.equal(prebidServerResponse); + }); + + it('returns a valid Prebid API response object for a video Prebid Server response', function () { + const prebidServerResponse = Object.freeze({ + body: [{ + mediaType: 'video', + requestId: '67890', + cpm: 2, + currency: 'USD', + width: 600, + height: 300, + ad: VIDEO_AD, + ttl: 300, + creativeId: '248e8e0c-2f17-421d-84db-5c9814bf4a79', + netRevenue: false, + meta: { + advertiser: '105600', + width: 300, + height: 250, + isCustom: '1', + progressBar: false, + mpuSrc: '//images.intellitxt.com/a/105600/Genpact/genpact.jpg', + clickURL: '{{click}}' + }, + vastUrl: 'https://www.example.com/myVastVideo' + }] + }); + + const interpretedResponse = spec.interpretResponse(prebidServerResponse, {}); + + expect(interpretedResponse).to.be.a('array'); + expect(interpretedResponse.length).to.equal(1); + + const interpretedBid = interpretedResponse[0]; + + expect(interpretedBid.mediaType).to.equal('video'); + expect(interpretedBid.requestId).to.equal('67890'); + expect(interpretedBid.cpm).to.equal(2); + expect(interpretedBid.currency).to.equal('USD'); + expect(interpretedBid.width).to.equal(600); + expect(interpretedBid.height).to.equal(300); + expect(interpretedBid.ad).to.equal(VIDEO_AD); + expect(interpretedBid.ttl).to.equal(300); + expect(interpretedBid.creativeId).to.equal('248e8e0c-2f17-421d-84db-5c9814bf4a79'); + expect(interpretedBid.netRevenue).to.be.false; + expect(interpretedBid.meta).to.deep.equal(prebidServerResponse.body[0].meta); + expect(interpretedBid.renderer).to.be.undefined; + expect(interpretedBid.adResponse).to.deep.equal(prebidServerResponse); + }); + + it('returns a valid Prebid API response object for a native Prebid Server response', function () { + const prebidServerResponse = Object.freeze({ + body: [{ + mediaType: 'native', + requestId: '13579', + cpm: 3, + currency: 'USD', + width: 240, + height: 300, + ad: 'https://www.example.com/native-display.html', + ttl: 300, + creativeId: 'd28e8e0c-2f17-421d-84db-5c9814bf4a81', + netRevenue: false, + meta: {}, + title: 'Test native ad bid for 13579', + sponsoredBy: 'Vibrant Media Ltd', + clickUrl: 'https://www.example.com/native-ct.html', + image: { + url: 'https://www.example.com/native-display.html', + width: 240, + height: 300 + } + }] + }); + + const interpretedResponse = spec.interpretResponse(prebidServerResponse, {}); + + expect(interpretedResponse).to.be.a('array'); + expect(interpretedResponse.length).to.equal(1); + + const interpretedBid = interpretedResponse[0]; + + expect(interpretedBid.mediaType).to.equal('native'); + expect(interpretedBid.requestId).to.equal('13579'); + expect(interpretedBid.cpm).to.equal(3); + expect(interpretedBid.currency).to.equal('USD'); + expect(interpretedBid.width).to.equal(240); + expect(interpretedBid.height).to.equal(300); + expect(interpretedBid.ad).to.equal('https://www.example.com/native-display.html'); + expect(interpretedBid.ttl).to.equal(300); + expect(interpretedBid.creativeId).to.equal('d28e8e0c-2f17-421d-84db-5c9814bf4a81'); + expect(interpretedBid.netRevenue).to.be.false; + expect(interpretedBid.meta).to.deep.equal(prebidServerResponse.body[0].meta); + expect(interpretedBid.renderer).to.be.undefined; + expect(interpretedBid.adResponse).to.deep.equal(prebidServerResponse); + }); + + it('returns a valid Prebid API response object for a multi-bid Prebid Server response', function () { + const prebidServerResponse = Object.freeze({ + body: [ + { + mediaType: 'banner', + requestId: '12345', + cpm: 3, + currency: 'USD', + width: 640, + height: 240, + ad: BANNER_AD, + ttl: 300, + creativeId: '86f4aef9-2f17-421d-84db-5c9814bf4a79', + netRevenue: false, + meta: { + advertiser: '105600', + width: 300, + height: 250, + isCustom: '1', + progressBar: false, + mpuSrc: '//images.intellitxt.com/a/105600/Genpact/genpact.jpg', + clickURL: '{{click}}' + } + }, + { + mediaType: 'video', + requestId: '67890', + cpm: 4, + currency: 'USD', + width: 300, + height: 300, + ad: VIDEO_AD, + ttl: 300, + creativeId: 'd28e8e0c-2f17-421d-84db-5c9814bf4a79', + netRevenue: false, + meta: { + advertiser: '105600', + width: 300, + height: 250, + isCustom: '1', + progressBar: false, + mpuSrc: '//images.intellitxt.com/a/105600/Genpact/genpact.jpg', + clickURL: '{{click}}' + }, + vastUrl: 'https://www.example.com/myVastVideo' + }, + { + mediaType: 'native', + requestId: '13579', + cpm: 5, + currency: 'USD', + width: 640, + height: 240, + ad: 'https://www.example.com/native-display.html', + ttl: 300, + creativeId: '888e8e0c-2f17-421d-84db-5c9814bf4a81', + netRevenue: false, + meta: {}, + title: 'Test native ad bid for 13579', + sponsoredBy: 'Vibrant Media Ltd', + clickUrl: 'https://www.example.com/native-ct.html', + image: { + url: 'https://www.example.com/native-display.html', + width: 640, + height: 240 + } + } + ] + }); + + const interpretedResponse = spec.interpretResponse(prebidServerResponse, {}); + + expect(interpretedResponse).to.be.a('array'); + expect(interpretedResponse.length).to.equal(3); + + const interpretedBannerBid = interpretedResponse[0]; + + expect(interpretedBannerBid.mediaType).to.equal('banner'); + expect(interpretedBannerBid.requestId).to.equal('12345'); + expect(interpretedBannerBid.cpm).to.equal(3); + expect(interpretedBannerBid.currency).to.equal('USD'); + expect(interpretedBannerBid.width).to.equal(640); + expect(interpretedBannerBid.height).to.equal(240); + expect(interpretedBannerBid.ad).to.equal(BANNER_AD); + expect(interpretedBannerBid.ttl).to.equal(300); + expect(interpretedBannerBid.creativeId).to.equal('86f4aef9-2f17-421d-84db-5c9814bf4a79'); + expect(interpretedBannerBid.netRevenue).to.be.false; + expect(interpretedBannerBid.meta).to.deep.equal(prebidServerResponse.body[0].meta); + expect(interpretedBannerBid.renderer).to.be.undefined; + expect(interpretedBannerBid.adResponse).to.deep.equal(prebidServerResponse); + + const interpretedVideoBid = interpretedResponse[1]; + + expect(interpretedVideoBid.mediaType).to.equal('video'); + expect(interpretedVideoBid.requestId).to.equal('67890'); + expect(interpretedVideoBid.cpm).to.equal(4); + expect(interpretedVideoBid.currency).to.equal('USD'); + expect(interpretedVideoBid.width).to.equal(300); + expect(interpretedVideoBid.height).to.equal(300); + expect(interpretedVideoBid.ad).to.equal(VIDEO_AD); + expect(interpretedVideoBid.ttl).to.equal(300); + expect(interpretedVideoBid.creativeId).to.equal('d28e8e0c-2f17-421d-84db-5c9814bf4a79'); + expect(interpretedVideoBid.netRevenue).to.be.false; + expect(interpretedVideoBid.meta).to.deep.equal(prebidServerResponse.body[1].meta); + expect(interpretedVideoBid.renderer).to.be.undefined; + expect(interpretedVideoBid.adResponse).to.deep.equal(prebidServerResponse); + + const interpretedNativeBid = interpretedResponse[2]; + + expect(interpretedNativeBid.mediaType).to.equal('native'); + expect(interpretedNativeBid.requestId).to.equal('13579'); + expect(interpretedNativeBid.cpm).to.equal(5); + expect(interpretedNativeBid.currency).to.equal('USD'); + expect(interpretedNativeBid.width).to.equal(640); + expect(interpretedNativeBid.height).to.equal(240); + expect(interpretedNativeBid.ad).to.equal('https://www.example.com/native-display.html'); + expect(interpretedNativeBid.ttl).to.equal(300); + expect(interpretedNativeBid.creativeId).to.equal('888e8e0c-2f17-421d-84db-5c9814bf4a81'); + expect(interpretedNativeBid.netRevenue).to.be.false; + expect(interpretedNativeBid.meta).to.deep.equal(prebidServerResponse.body[2].meta); + expect(interpretedNativeBid.renderer).to.be.undefined; + expect(interpretedNativeBid.adResponse).to.deep.equal(prebidServerResponse); + }); + }); + + describe('Flow tests', function () { + describe('For successive API calls to the public functions', function () { + it('should succeed with one media type per bid', function () { + const transformedBannerBidParams = spec.transformBidParams(VALID_BANNER_BID_PARAMS); + const transformedVideoBidParams = spec.transformBidParams(VALID_VIDEO_BID_PARAMS); + const transformedNativeBidParams = spec.transformBidParams(VALID_NATIVE_BID_PARAMS); + + const bannerBid = { + bidder: 'vibrantmedia', + params: transformedBannerBidParams, + mediaTypes: { + banner: { + sizes: DEFAULT_BID_SIZES, + }, + }, + adUnitCode: 'banner-div', + bidId: '30b31c1838de11', + bidderRequestId: '22edbae2733bf6', + placementId: '293857832abfef', + auctionId: '1d1a030790a475', + }; + const videoBid = { + bidder: 'vibrantmedia', + params: transformedVideoBidParams, + mediaTypes: { + video: { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }, + }, + adUnitCode: 'video-div', + bidId: '30b31c1838de15', + bidderRequestId: '22edbae2733bf6', + placementId: '293857832abfef', + auctionId: '1d1a030790a475', + }; + const nativeBid = { + bidder: 'vibrantmedia', + params: transformedNativeBidParams, + mediaTypes: { + native: { + image: { + required: true, + sizes: [300, 250] + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + } + } + }, + adUnitCode: 'native-div', + bidId: '30b31c1838de12', + bidderRequestId: '22edbae2733bf6', + placementId: '293857832abfef', + auctionId: '1d1a030790a475', + }; + + expect(spec.isBidRequestValid(bannerBid)).to.be.true; + expect(spec.isBidRequestValid(videoBid)).to.be.true; + expect(spec.isBidRequestValid(nativeBid)).to.be.true; + + const bidRequests = [bannerBid, videoBid, nativeBid]; + const validBidderRequest = getValidBidderRequest(bidRequests); + const serverRequest = spec.buildRequests(bidRequests, validBidderRequest); + expect(serverRequest.method).to.equal('POST'); + expect(serverRequest.url).to.equal(EXPECTED_PREBID_SERVER_URL); + + const payload = JSON.parse(serverRequest.data); + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(3); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bannerBid.adUnitCode); + expect(payload.biddata[0].id).to.equal(bannerBid.placementId); + expect(payload.biddata[0].bidder).to.equal(bannerBid.bidder); + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[1]).to.exist; + expect(payload.biddata[1].code).to.equal(videoBid.adUnitCode); + expect(payload.biddata[1].id).to.equal(videoBid.placementId); + expect(payload.biddata[1].bidder).to.equal(videoBid.bidder); + expect(payload.biddata[1].mediaTypes[VIDEO]).to.exist; + expect(payload.biddata[1].mediaTypes[VIDEO]).to.deep.equal({ + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES + }); + expect(payload.biddata[2]).to.exist; + expect(payload.biddata[2].code).to.equal(nativeBid.adUnitCode); + expect(payload.biddata[2].id).to.equal(nativeBid.placementId); + expect(payload.biddata[2].bidder).to.equal(nativeBid.bidder); + expect(payload.biddata[2].mediaTypes[NATIVE]).to.exist; + expect(payload.biddata[2].mediaTypes[NATIVE]).to.deep.equal(nativeBid.mediaTypes.native); + + // From here, the API would call the Prebid Server and call interpretResponse, which is covered by tests elsewhere + }); + + it('should succeed with multiple media types for a single bid', function () { + const bidParams = spec.transformBidParams(VALID_VIDEO_BID_PARAMS); + const bid = { + bidder: 'vibrantmedia', + params: bidParams, + mediaTypes: { + banner: { + sizes: DEFAULT_BID_SIZES + }, + video: { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES + }, + native: { + sizes: DEFAULT_BID_SIZES + } + }, + adUnitCode: 'test-div', + bidId: '30b31c1838de13', + bidderRequestId: '22edbae2733bf6', + placementId: '293857832abfef', + auctionId: '1d1a030790a475', + }; + + expect(spec.isBidRequestValid(bid)).to.be.true; + + const bidRequests = [bid]; + const validBidderRequest = getValidBidderRequest(bidRequests); + const serverRequest = spec.buildRequests(bidRequests, validBidderRequest); + expect(serverRequest.method).to.equal('POST'); + expect(serverRequest.url).to.equal(EXPECTED_PREBID_SERVER_URL); + + const payload = JSON.parse(serverRequest.data); + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(1); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bid.adUnitCode); + expect(payload.biddata[0].id).to.equal(bid.placementId); + expect(payload.biddata[0].bidder).to.equal(bid.bidder); + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[0].mediaTypes[VIDEO]).to.exist; + expect(payload.biddata[0].mediaTypes[VIDEO]).to.deep.equal({ + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[0].mediaTypes[NATIVE]).to.exist; + expect(payload.biddata[0].mediaTypes[NATIVE]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + + // From here, the API would call the Prebid Server and call interpretResponse, which is covered by tests elsewhere + }); + }); + }); +}); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index d7f20c434ca..0b5dadce09f 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -37,7 +37,8 @@ const BID = { 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', 'sizes': [[300, 250], [300, 600]], 'bidderRequestId': '1fdb5ff1b6eaa7', - 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a' + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc' }; const BIDDER_REQUEST = { @@ -60,6 +61,7 @@ const SERVER_RESPONSE = { 'exp': 30, 'width': 300, 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], 'cookies': [{ 'src': 'https://sync.com', 'type': 'iframe' @@ -162,6 +164,7 @@ describe('VidazooBidAdapter', function () { uniqueDealId: `${hashUrl}_${Date.now().toString()}`, bidderVersion: adapter.version, prebidVersion: version, + schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', @@ -221,7 +224,10 @@ describe('VidazooBidAdapter', function () { currency: 'USD', netRevenue: true, ttl: 30, - ad: '' + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } }); }); diff --git a/test/spec/modules/videoNowBidAdapter_spec.js b/test/spec/modules/videoNowBidAdapter_spec.js deleted file mode 100644 index a419c73456b..00000000000 --- a/test/spec/modules/videoNowBidAdapter_spec.js +++ /dev/null @@ -1,599 +0,0 @@ -import { expect } from 'chai' -import { spec } from 'modules/videoNowBidAdapter.js' -import { replaceAuctionPrice } from 'src/utils.js' -import * as utils from 'src/utils.js'; - -// childNode.remove polyfill for ie11 -// suggested by: https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove - -// from:https://github.com/jserz/js_piece/blob/master/DOM/ChildNode/remove()/remove().md -(function (arr) { - arr.forEach(function (item) { - if (item.hasOwnProperty('remove')) { - return; - } - Object.defineProperty(item, 'remove', { - configurable: true, - enumerable: true, - writable: true, - value: function remove() { - if (this.parentNode === null) { - return; - } - this.parentNode.removeChild(this); - } - }); - }); -})([Element.prototype, CharacterData.prototype, DocumentType.prototype]); - -const placementId = 'div-gpt-ad-1438287399331-1' -const LS_ITEM_NAME = 'videonow-config' - -const getValidServerResponse = () => { - const serverResponse = { - body: { - id: '111-111', - bidid: '2955a162-699e-4811-ce88-5c3ac973e73c', - cur: 'RUB', - seatbid: [ - { - bid: [ - { - id: 'e3bf2b82e3e9485113fad6c9b27f8768.1', - impid: '1', - price: 10.97, - nurl: 'https://localhost:8086/event/nurl', - netRevenue: false, - ttl: 800, - adm: '', - crid: 'e3bf2b82e3e9485113fad6c9b27f8768.1', - h: 640, - w: 480, - ext: { - init: 'https://localhost:8086/vn_init.js', - module: { - min: 'https://localhost:8086/vn_module.js', - log: 'https://localhost:8086/vn_module.js?log=1' - }, - format: { - name: 'flyRoll', - }, - }, - - }, - ], - group: 0, - }, - ], - price: 10, - ext: { - placementId, - pixels: [ - 'https://localhost:8086/event/pxlcookiematching?uiid=1', - 'https://localhost:8086/event/pxlcookiematching?uiid=2', - ], - iframes: [ - 'https://localhost:8086/event/ifrcookiematching?uiid=1', - 'https://localhost:8086/event/ifrcookiematching?uiid=2', - ], - }, - }, - headers: {}, - } - - return JSON.parse(JSON.stringify(serverResponse)) -} - -describe('videonowAdapterTests', function() { - describe('bidRequestValidity', function() { - it('bidRequest with pId', function() { - expect(spec.isBidRequestValid({ - bidder: 'videonow', - params: { - pId: '86858', - }, - })).to.equal(true) - }) - - it('bidRequest without pId', function() { - expect(spec.isBidRequestValid({ - bidder: 'videonow', - params: { - nomater: 86858, - }, - })).to.equal(false) - - it('bidRequest is empty', function() { - expect(spec.isBidRequestValid({})).to.equal(false) - }) - - it('bidRequest is undefned', function() { - expect(spec.isBidRequestValid(undefined)).to.equal(false) - }) - }) - - describe('bidRequest', function() { - const validBidRequests = [ - { - bidder: 'videonow', - params: { - pId: '1', - placementId, - url: 'https://localhost:8086/bid?p=exists', - bidFloor: 10, - cur: 'RUB' - }, - crumbs: { - pubcid: 'feded041-35dd-4b54-979a-6d7805abfa75', - }, - mediaTypes: { - banner: { - sizes: [[640, 480], [320, 200]] - }, - }, - adUnitCode: 'test-ad', - transactionId: '676403c7-09c9-4b56-be82-e7cae81f40b9', - sizes: [[640, 480], [320, 200]], - bidId: '268c309f46390d', - bidderRequestId: '1dfdd514c36ef6', - auctionId: '4d523546-889a-4029-9a79-13d3c69f9922', - src: 'client', - bidRequestsCount: 1, - }, - ] - - const bidderRequest = { - bidderCode: 'videonow', - auctionId: '4d523546-889a-4029-9a79-13d3c69f9922', - bidderRequestId: '1dfdd514c36ef6', - bids: [ - { - bidder: 'videonow', - params: { - pId: '1', - placementId, - url: 'https://localhost:8086/bid', - bidFloor: 10, - cur: 'RUB', - }, - crumbs: { - pubcid: 'feded041-35dd-4b54-979a-6d7805abfa75', - }, - mediaTypes: { - banner: { - sizes: [[640, 480], [320, 200]], - }, - }, - adUnitCode: 'test-ad', - transactionId: '676403c7-09c9-4b56-be82-e7cae81f40b9', - sizes: [[640, 480], [320, 200]], - bidId: '268c309f46390d', - bidderRequestId: '1dfdd514c36ef6', - auctionId: '4d523546-889a-4029-9a79-13d3c69f9922', - src: 'client', - bidRequestsCount: 1, - }, - ], - auctionStart: 1565794308584, - timeout: 3000, - refererInfo: { - referer: 'https://localhost:8086/page', - reachedTop: true, - numIframes: 0, - stack: [ - 'https://localhost:8086/page', - ], - }, - start: 1565794308589, - } - - const requests = spec.buildRequests(validBidRequests, bidderRequest) - const request = (requests && requests.length && requests[0]) || {} - - it('bidRequest count', function() { - expect(requests.length).to.equal(1) - }) - - it('bidRequest method', function() { - expect(request.method).to.equal('POST') - }) - - it('bidRequest url', function() { - expect(request.url).to.equal('https://localhost:8086/bid?p=exists&profile_id=1') - }) - - it('bidRequest data', function() { - const data = request.data - expect(data.aid).to.be.eql(validBidRequests[0].params.aid) - expect(data.id).to.be.eql(validBidRequests[0].bidId) - expect(data.sizes).to.be.eql(validBidRequests[0].sizes) - }) - - describe('bidRequest advanced', function() { - const bidderRequestEmptyParamsAndExtParams = { - bidder: 'videonow', - params: { - pId: '1', - }, - ext: { - p1: 'ext1', - p2: 'ext2', - }, - } - - it('bidRequest count', function() { - const requests = spec.buildRequests([bidderRequestEmptyParamsAndExtParams], bidderRequest) - expect(requests.length).to.equal(1) - }) - - it('bidRequest default url', function() { - const requests = spec.buildRequests([bidderRequestEmptyParamsAndExtParams], bidderRequest) - const request = (requests && requests.length && requests[0]) || {} - expect(request.url).to.equal('https://bidder.videonow.ru/prebid?profile_id=1') - }) - - it('bidRequest default currency', function() { - const requests = spec.buildRequests([bidderRequestEmptyParamsAndExtParams], bidderRequest) - const request = (requests && requests.length && requests[0]) || {} - const data = (request && request.data) || {} - expect(data.cur).to.equal('RUB') - }) - - it('bidRequest ext parameters ', function() { - const requests = spec.buildRequests([bidderRequestEmptyParamsAndExtParams], bidderRequest) - const request = (requests && requests.length && requests[0]) || {} - const data = (request && request.data) || {} - expect(data['ext_p1']).to.equal('ext1') - expect(data['ext_p2']).to.equal('ext2') - }) - - it('bidRequest without params', function() { - const bidderReq = { - bidder: 'videonow', - } - const requests = spec.buildRequests([bidderReq], bidderRequest) - expect(requests.length).to.equal(1) - }) - }) - }) - - describe('onBidWon', function() { - const cpm = 10 - const nurl = 'https://fakedomain.nld?price=${AUCTION_PRICE}' - const imgSrc = replaceAuctionPrice(nurl, cpm) - - beforeEach(function() { - sinon.stub(utils, 'triggerPixel') - }) - - afterEach(function() { - utils.triggerPixel.restore() - }) - - it('Should not create nurl pixel if bid is undefined', function() { - spec.onBidWon() - expect(utils.triggerPixel.called).to.equal(false); - }) - - it('Should not create nurl pixel if bid does not contains nurl', function() { - spec.onBidWon({}) - expect(utils.triggerPixel.called).to.equal(false); - }) - - it('Should create nurl pixel if bid nurl', function() { - spec.onBidWon({ nurl, cpm }) - expect(utils.triggerPixel.calledWith(imgSrc)).to.equal(true); - }) - }) - - describe('getUserSyncs', function() { - it('Should return an empty array if not get serverResponses', function() { - expect(spec.getUserSyncs({}).length).to.equal(0) - }) - - it('Should return an empty array if get serverResponses as empty array', function() { - expect(spec.getUserSyncs({}, []).length).to.equal(0) - }) - - it('Should return an empty array if serverResponses has no body', function() { - const serverResp = getValidServerResponse() - delete serverResp.body - const syncs = spec.getUserSyncs({}, [serverResp]) - expect(syncs.length).to.equal(0) - }) - - it('Should return an empty array if serverResponses has no ext', function() { - const serverResp = getValidServerResponse() - delete serverResp.body.ext - const syncs = spec.getUserSyncs({}, [serverResp]) - expect(syncs.length).to.equal(0) - }) - - it('Should return an array', function() { - const serverResp = getValidServerResponse() - const syncs = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [serverResp]) - expect(syncs.length).to.equal(4) - }) - - it('Should return pixels', function() { - const serverResp = getValidServerResponse() - const syncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [serverResp]) - expect(syncs.length).to.equal(2) - expect(syncs[0].type).to.equal('image') - expect(syncs[1].type).to.equal('image') - }) - - it('Should return iframes', function() { - const serverResp = getValidServerResponse() - const syncs = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [serverResp]) - expect(syncs.length).to.equal(2) - expect(syncs[0].type).to.equal('iframe') - expect(syncs[1].type).to.equal('iframe') - }) - }) - - describe('interpretResponse', function() { - const bidRequest = { - method: 'POST', - url: 'https://localhost:8086/bid?profile_id=1', - data: { - id: '217b8ab59a18e8', - cpm: 10, - sizes: [[640, 480], [320, 200]], - cur: 'RUB', - placementId, - ref: 'https://localhost:8086/page', - }, - } - - it('Should have only one bid', function() { - const serverResponse = getValidServerResponse() - const result = spec.interpretResponse(serverResponse, bidRequest) - expect(result.length).to.equal(1) - }) - - it('Should have required keys', function() { - const serverResponse = getValidServerResponse() - const result = spec.interpretResponse(serverResponse, bidRequest) - const bid = serverResponse.body.seatbid[0].bid[0] - const res = result[0] - expect(res.requestId).to.be.eql(bidRequest.data.id) - expect(res.cpm).to.be.eql(bid.price) - expect(res.creativeId).to.be.eql(bid.crid) - expect(res.netRevenue).to.be.a('boolean') - expect(res.ttl).to.be.eql(bid.ttl) - expect(res.renderer).to.be.a('Object') - expect(res.renderer.render).to.be.a('function') - }) - - it('Should return an empty array if empty or no bids in response', function() { - expect(spec.interpretResponse({ body: '' }, {}).length).to.equal(0) - }) - - it('Should return an empty array if bidRequest\'s data is absent', function() { - const serverResponse = getValidServerResponse() - expect(spec.interpretResponse(serverResponse, undefined).length).to.equal(0) - }) - - it('Should return an empty array if bidRequest\'s data is not contains bidId ', function() { - const serverResponse = getValidServerResponse() - expect(spec.interpretResponse(serverResponse, { data: {} }).length).to.equal(0) - }) - - it('Should return an empty array if bidRequest\'s data bidId is undefined', function() { - const serverResponse = getValidServerResponse() - expect(spec.interpretResponse(serverResponse, { data: { id: null } }).length).to.equal(0) - }) - - it('Should return an empty array if serverResponse do not contains seatbid', function() { - expect(spec.interpretResponse({ body: {} }, bidRequest).length).to.equal(0) - }) - - it('Should return an empty array if serverResponse\'s seatbid is empty', function() { - expect(spec.interpretResponse({ body: { seatbid: [] } }, bidRequest).length).to.equal(0) - }) - - it('Should return an empty array if serverResponse\'s placementId is undefined', function() { - expect(spec.interpretResponse({ body: { seatbid: [1, 2] } }, bidRequest).length).to.equal(0) - }) - - it('Should return an empty array if serverResponse\'s id in the bid is undefined', function() { - const serverResp = getValidServerResponse() - delete serverResp.body.seatbid[0].bid[0].id - let res = spec.interpretResponse(serverResp, bidRequest) - expect(res.length).to.equal(0) - }) - - it('Should return an empty array if serverResponse\'s price in the bid is undefined', function() { - const serverResp = getValidServerResponse() - delete serverResp.body.seatbid[0].bid[0].price - const res = spec.interpretResponse(serverResp, bidRequest) - expect(res.length).to.equal(0) - }) - - it('Should return an empty array if serverResponse\'s price in the bid is 0', function() { - const serverResp = getValidServerResponse() - serverResp.body.seatbid[0].bid[0].price = 0 - const res = spec.interpretResponse(serverResp, bidRequest) - - expect(res.length).to.equal(0) - }) - - it('Should return an empty array if serverResponse\'s init in the bid\'s ext is undefined', function() { - const serverResp = getValidServerResponse() - delete serverResp.body.seatbid[0].bid[0].ext.init - const res = spec.interpretResponse(serverResp, bidRequest) - - expect(res.length).to.equal(0) - }) - - it('Should return an empty array if serverResponse\'s module in the bid\'s ext is undefined', function() { - const serverResp = getValidServerResponse() - delete serverResp.body.seatbid[0].bid[0].ext.module - const res = spec.interpretResponse(serverResp, bidRequest) - - expect(res.length).to.equal(0) - }) - - it('Should return an empty array if serverResponse\'s adm in the bid is undefined', function() { - const serverResp = getValidServerResponse() - delete serverResp.body.seatbid[0].bid[0].adm - const res = spec.interpretResponse(serverResp, bidRequest) - - expect(res.length).to.equal(0) - }) - - it('Should return an empty array if serverResponse\'s the bid\'s ext is undefined', function() { - const serverResp = getValidServerResponse() - delete serverResp.body.seatbid[0].bid[0].ext - const res = spec.interpretResponse(serverResp, bidRequest) - - expect(res.length).to.equal(0) - }) - - it('Default ttl is 300', function() { - const serverResp = getValidServerResponse() - delete serverResp.body.seatbid[0].bid[0].ttl - const res = spec.interpretResponse(serverResp, bidRequest) - expect(res.length).to.equal(1) - expect(res[0].ttl).to.equal(300) - }) - - it('Default netRevenue is true', function() { - const serverResp = getValidServerResponse() - delete serverResp.body.seatbid[0].bid[0].netRevenue - const res = spec.interpretResponse(serverResp, bidRequest) - expect(res.length).to.equal(1) - expect(res[0].netRevenue).to.be.true; - }) - - it('Default currency is RUB', function() { - const serverResp = getValidServerResponse() - delete serverResp.body.cur - const res = spec.interpretResponse(serverResp, bidRequest) - expect(res.length).to.equal(1) - expect(res[0].currency).to.equal('RUB') - }) - - describe('different module paths', function() { - beforeEach(function() { - window.localStorage && localStorage.setItem(LS_ITEM_NAME, '{}') - }) - - afterEach(function() { - const serverResp = getValidServerResponse() - const { module: { log, min }, init } = serverResp.body.seatbid[0].bid[0].ext - remove(init) - remove(log) - remove(min) - - function remove(src) { - if (!src) return - const d = document.querySelectorAll(`script[src^="${src}"]`) - // using the Array.prototype.forEach as a workaround for IE11... - // see https://developer.mozilla.org/en-US/docs/Web/API/NodeList - d && d.length && Array.prototype.forEach.call(d, el => el && el.remove()) - } - }) - - it('should use prod module by default', function() { - const serverResp = getValidServerResponse() - const res = spec.interpretResponse(serverResp, bidRequest) - expect(res.length).to.equal(1) - - const renderer = res[0].renderer - expect(renderer).to.be.an('object') - expect(renderer.url).to.equal(serverResp.body.seatbid[0].bid[0].ext.module.min) - }) - - it('should use "log" module if "prod" is not exists', function() { - const serverResp = getValidServerResponse() - delete serverResp.body.seatbid[0].bid[0].ext.module.min - const res = spec.interpretResponse(serverResp, bidRequest) - expect(res.length).to.equal(1) - - const renderer = res[0].renderer - expect(renderer).to.be.an('object') - expect(renderer.url).to.equal(serverResp.body.seatbid[0].bid[0].ext.module.log) - }) - - it('should correct combine src for init', function() { - const serverResp = getValidServerResponse() - - const src = `${serverResp.body.seatbid[0].bid[0].ext.init}?profileId=1` - const placementElement = document.createElement('div') - placementElement.setAttribute('id', placementId) - - const resp = spec.interpretResponse(serverResp, bidRequest) - expect(resp.length).to.equal(1) - - const renderer = resp[0].renderer - expect(renderer).to.be.an('object') - - document.body.appendChild(placementElement) - - renderer.render() - - // const res = document.querySelectorAll(`script[src="${src}"]`) - // expect(res.length).to.equal(1) - }) - - it('should correct combine src for init if init url contains "?"', function() { - const serverResp = getValidServerResponse() - - serverResp.body.seatbid[0].bid[0].ext.init += '?div=1' - const src = `${serverResp.body.seatbid[0].bid[0].ext.init}&profileId=1` - - const placementElement = document.createElement('div') - placementElement.setAttribute('id', placementId) - - const resp = spec.interpretResponse(serverResp, bidRequest) - expect(resp.length).to.equal(1) - - const renderer = resp[0].renderer - expect(renderer).to.be.an('object') - - document.body.appendChild(placementElement) - - renderer.render() - - // const res = document.querySelectorAll(`script[src="${src}"]`) - // expect(res.length).to.equal(1) - }) - }) - - describe('renderer object', function() { - it('execute renderer.render() should create window.videonow object', function() { - const serverResp = getValidServerResponse() - const res = spec.interpretResponse(serverResp, bidRequest) - expect(res.length).to.equal(1) - - const renderer = res[0].renderer - expect(renderer).to.be.an('object') - expect(renderer.render).to.a('function') - - const doc = window.document - const placementElement = doc.createElement('div') - placementElement.setAttribute('id', placementId) - doc.body.appendChild(placementElement) - - renderer.render() - expect(window.videonow).to.an('object') - }) - }) - - it('execute renderer.render() should not create window.videonow object if placement element not found', function() { - const serverResp = getValidServerResponse() - const res = spec.interpretResponse(serverResp, bidRequest) - expect(res.length).to.equal(1) - - const renderer = res[0].renderer - expect(renderer).to.be.an('object') - expect(renderer.render).to.a('function') - - renderer.render() - expect(window.videonow).to.be.undefined - }) - }) - }) -}) diff --git a/test/spec/modules/videobyteBidAdapter_spec.js b/test/spec/modules/videobyteBidAdapter_spec.js new file mode 100644 index 00000000000..f7ea0698956 --- /dev/null +++ b/test/spec/modules/videobyteBidAdapter_spec.js @@ -0,0 +1,626 @@ +import { expect } from 'chai'; +import { spec } from 'modules/videobyteBidAdapter.js'; + +describe('VideoByteBidAdapter', function () { + let bidRequest; + let bidderRequest = { + 'bidderCode': 'videobyte', + 'auctionId': 'e158486f-8c7f-472f-94ce-b0cbfbb50ab4', + 'bidderRequestId': '1e498b84fffc39', + 'bids': bidRequest, + 'auctionStart': 1520001292880, + 'timeout': 3000, + 'start': 1520001292884, + 'doneCbCallCount': 0, + 'refererInfo': { + 'numIframes': 1, + 'reachedTop': true, + 'referer': 'test.com' + } + }; + let mockConfig; + + beforeEach(function () { + bidRequest = { + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + } + }, + bidder: 'videobyte', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + adUnitCode: 'video1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + sid: 134, + rewarded: 1, + placement: 1, + hp: 1, + inventoryid: 123 + }, + site: { + id: 1, + page: 'https://test.com', + referrer: 'http://test.com' + }, + pubId: 'vb12345' + } + }; + }); + + describe('spec.isBidRequestValid', function () { + it('should return false when mediaTypes is empty', function () { + bidRequest.mediaTypes = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return true (skip validations) when e2etest = true', function () { + bidRequest.params.video = { + e2etest: true + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when mediaTypes.video has all mandatory params', function () { + bidRequest.mediaTypes.video = { + context: 'instream', + playerSize: [[640, 480]], + mimes: ['video/mp4', 'application/javascript'], + } + bidRequest.params.video = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when params.video has all override params instead of mediaTypes.video', function () { + bidRequest.mediaTypes.video = { + context: 'instream' + }; + bidRequest.params.video = { + playerSize: [[640, 480]], + mimes: ['video/mp4', 'application/javascript'] + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when mimes is passed in params.video', function () { + bidRequest.mediaTypes.video = { + context: 'instream', + playerSize: [[640, 480]] + }; + bidRequest.video = { + mimes: ['video/mp4', 'application/javascript'] + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false when both mediaTypes.video and params.video Objects are missing', function () { + bidRequest.mediaTypes = {}; + bidRequest.params = { + pubId: 'brxd' + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when both mediaTypes.video and params.video are missing mimes and player size', function () { + bidRequest.mediaTypes = { + video: { + context: 'instream' + } + }; + bidRequest.params = { + pubId: 'brxd' + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when the "pubId" param is missing', function () { + bidRequest.params = { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + it('should return false when the "pubId" param is missing', function () { + bidRequest.params = { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when no bid params are passed', function () { + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('spec.buildRequests', function () { + it('should create a POST request for every bid', function () { + const requests = spec.buildRequests([bidRequest], bidderRequest); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(spec.ENDPOINT + '?pid=' + bidRequest.params.pubId); + }); + + it('should attach request data', function () { + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + const [width, height] = bidRequest.sizes; + const VERSION = '1.0.0'; + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); + expect(data.ext.prebidver).to.equal('$prebid.version$'); + expect(data.ext.adapterver).to.equal(spec.VERSION); + }); + + it('should set pubId to e2etest when bid.params.video.e2etest = true', function () { + bidRequest.params.video.e2etest = true; + const requests = spec.buildRequests([bidRequest], bidderRequest); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(spec.ENDPOINT + '?pid=e2etest'); + }); + + it('should attach End 2 End test data', function () { + bidRequest.params.video.e2etest = true; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).to.not.exist; + expect(data.imp[0].video.w).to.equal(640); + expect(data.imp[0].video.h).to.equal(480); + }); + + it('should send Global schain', function () { + bidRequest.params.video.sid = null; + const globalSchain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'some-platform.com', + sid: '111111', + rid: bidRequest.id, + hp: 1 + }] + }; + bidRequest.schain = globalSchain; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + const schain = data.source.ext.schain; + expect(schain.nodes.length).to.equal(1); + expect(schain).to.deep.equal(globalSchain); + }); + + describe('content object validations', function () { + it('should not accept content object if value is Undefined ', function () { + bidRequest.params.video.content = null; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.be.undefined; + }); + it('should not accept content object if value is is Array ', function () { + bidRequest.params.video.content = []; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.be.undefined; + }); + it('should not accept content object if value is Number ', function () { + bidRequest.params.video.content = 123456; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.be.undefined; + }); + it('should not accept content object if value is String ', function () { + bidRequest.params.video.content = 'keyValuePairs'; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.be.undefined; + }); + it('should not accept content object if value is Boolean ', function () { + bidRequest.params.video.content = true; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.be.undefined; + }); + it('should accept content object if value is Object ', function () { + bidRequest.params.video.content = {}; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.be.a('object'); + }); + + it('should not append unsupported content object keys', function () { + bidRequest.params.video.content = { + fake: 'news', + unreal: 'param', + counterfit: 'data' + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.be.empty; + }); + + it('should not append content string parameters if value is not string ', function () { + bidRequest.params.video.content = { + id: 1234, + title: ['Title'], + series: ['Series'], + season: ['Season'], + genre: ['Genre'], + contentrating: {1: 'C-Rating'}, + language: {1: 'EN'} + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.be.a('object'); + expect(data.site.content).to.be.empty + }); + it('should not append content Number parameters if value is not Number ', function () { + bidRequest.params.video.content = { + episode: '1', + context: 'context', + livestream: {0: 'stream'}, + len: [360], + prodq: [1], + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.be.a('object'); + expect(data.site.content).to.be.empty + }); + it('should not append content Array parameters if value is not Array ', function () { + bidRequest.params.video.content = { + cat: 'categories', + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.be.a('object'); + expect(data.site.content).to.be.empty + }); + it('should not append content ext if value is not Object ', function () { + bidRequest.params.video.content = { + ext: 'content.ext', + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.be.a('object'); + expect(data.site.content).to.be.empty + }); + it('should append supported parameters if value match validations ', function () { + bidRequest.params.video.content = { + id: '1234', + title: 'Title', + series: 'Series', + season: 'Season', + cat: [ + 'IAB1' + ], + genre: 'Genre', + contentrating: 'C-Rating', + language: 'EN', + episode: 1, + prodq: 1, + context: 1, + livestream: 0, + len: 360, + ext: {} + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.content).to.deep.equal(bidRequest.params.video.content); + }); + }); + }); + describe('price floor module validations', function () { + beforeEach(function () { + bidRequest.getFloor = (floorObj) => { + return { + floor: bidRequest.floors.values[floorObj.mediaType + '|640x480'], + currency: floorObj.currency, + mediaType: floorObj.mediaType + } + } + }); + + it('should get bidfloor from getFloor method', function () { + bidRequest.params.cur = 'EUR'; + bidRequest.floors = { + currency: 'EUR', + values: { + 'video|640x480': 5.55 + } + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(5.55); + }); + it('should get bidfloor from params method', function () { + bidRequest.params.bidfloor = 4.0; + bidRequest.params.currency = 'EUR'; + bidRequest.getFloor = null; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(4.0); + expect(data.imp[0].bidfloorcur).to.equal('EUR'); + }); + + it('should use adUnit/module currency & floor instead of bid.params.bidfloor', function () { + bidRequest.params.cur = 'EUR'; + bidRequest.params.bidfloor = 3.33; + bidRequest.floors = { + currency: 'EUR', + values: { + 'video|640x480': 5.55 + } + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(5.55); + }); + + it('should load video floor when multi-format adUnit is present', function () { + bidRequest.params.cur = 'EUR'; + bidRequest.mediaTypes.banner = { + sizes: [ + [640, 480] + ] + }; + bidRequest.floors = { + currency: 'EUR', + values: { + 'banner|640x480': 2.22, + 'video|640x480': 9.99 + } + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(9.99); + }) + }) + + describe('spec.interpretResponse', function () { + it('should return no bids if the response is not valid', function () { + const bidResponse = spec.interpretResponse({ + body: null + }, { + bidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "nurl" and "adm" are missing', function () { + const serverResponse = { + seatbid: [{ + bid: [{ + price: 6.01 + }] + }] + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + bidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "price" is missing', function () { + const serverResponse = { + seatbid: [{ + bid: [{ + adm: '' + }] + }] + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + bidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid video bid response with just "adm"', function () { + const serverResponse = { + id: '123', + seatbid: [{ + bid: [{ + id: 1, + adid: 123, + crid: 2, + price: 6.01, + adm: '', + adomain: [ + 'videobyte.com' + ], + w: 640, + h: 480 + }] + }], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + bidRequest + }); + let o = { + requestId: serverResponse.id, + bidderCode: spec.code, + cpm: serverResponse.seatbid[0].bid[0].price, + creativeId: serverResponse.seatbid[0].bid[0].crid, + vastXml: serverResponse.seatbid[0].bid[0].adm, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true, + meta: { + advertiserDomains: ['videobyte.com'] + } + }; + expect(bidResponse[0]).to.deep.equal(o); + }); + + it('should default ttl to 300', function () { + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse[0].ttl).to.equal(300); + }); + it('should not allow ttl above 3601, default to 300', function () { + bidRequest.params.video.ttl = 3601; + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse[0].ttl).to.equal(300); + }); + it('should not allow ttl below 1, default to 300', function () { + bidRequest.params.video.ttl = 0; + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse[0].ttl).to.equal(300); + }); + }); + + describe('when GDPR and uspConsent applies', function () { + beforeEach(function () { + bidderRequest = { + 'gdprConsent': { + 'consentString': 'test-gdpr-consent-string', + 'gdprApplies': true + }, + 'uspConsent': '1YN-', + 'bidderCode': 'videobyte', + 'auctionId': 'e158486f-8c7f-472f-94ce-b0cbfbb50ab4', + 'bidderRequestId': '1e498b84fffc39', + 'bids': bidRequest, + 'auctionStart': 1520001292880, + 'timeout': 3000, + 'start': 1520001292884, + 'doneCbCallCount': 0, + 'refererInfo': { + 'numIframes': 1, + 'reachedTop': true, + 'referer': 'test.com' + } + }; + + mockConfig = { + consentManagement: { + gdpr: { + cmpApi: 'iab', + timeout: 3000, + allowAuctionWithoutConsent: 'cancel' + }, + usp: { + cmpApi: 'iab', + timeout: 1000, + allowAuctionWithoutConsent: 'cancel' + } + } + }; + }); + + it('should send a signal to specify that GDPR applies to this request', function () { + const request = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(request[0].data); + expect(data.regs.ext.gdpr).to.equal(1); + }); + + it('should send the consent string', function () { + const request = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(request[0].data); + expect(data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); + + it('should send the uspConsent string', function () { + const request = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(request[0].data); + expect(data.regs.ext.us_privacy).to.equal(bidderRequest.uspConsent); + }); + + it('should send the uspConsent and GDPR ', function () { + const request = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(request[0].data); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.regs.ext.us_privacy).to.equal(bidderRequest.uspConsent); + }); + }); + + describe('getUserSyncs', function () { + const ortbResponse = { + 'body': { + 'ext': { + 'usersync': { + 'sovrn': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlsovrn', + 'type': 'iframe' + } + ] + }, + 'appnexus': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlappnexus', + 'type': 'pixel' + } + ] + } + } + } + } + }; + it('handles no parameters', function () { + let opts = spec.getUserSyncs({}); + expect(opts).to.be.an('array').that.is.empty; + }); + it('returns non if sync is not allowed', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + + expect(opts).to.be.an('array').that.is.empty; + }); + + it('iframe sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [ortbResponse]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal(ortbResponse.body.ext.usersync['sovrn'].syncs[0].url); + }); + + it('pixel sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [ortbResponse]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal(ortbResponse.body.ext.usersync['appnexus'].syncs[0].url); + }); + + it('all sync enabled should return only iframe result', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [ortbResponse]); + + expect(opts.length).to.equal(1); + }); + }); +}); diff --git a/test/spec/modules/videofyBidAdapter_spec.js b/test/spec/modules/videofyBidAdapter_spec.js deleted file mode 100644 index 270eefd1efc..00000000000 --- a/test/spec/modules/videofyBidAdapter_spec.js +++ /dev/null @@ -1,253 +0,0 @@ -import { spec } from 'modules/videofyBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import * as utils from '../../../src/utils.js'; - -const { expect } = require('chai'); - -describe('Videofy Bid Adapter Test', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'videofy', - 'params': { - 'AV_PUBLISHERID': '123456', - 'AV_CHANNELID': '123456' - }, - 'adUnitCode': 'video1', - 'sizes': [[300, 250], [640, 480]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', - 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - something: 'is wrong' - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let bid2Requests = [ - { - 'bidder': 'videofy', - 'params': { - 'AV_PUBLISHERID': '123456', - 'AV_CHANNELID': '123456' - }, - 'adUnitCode': 'test1', - 'sizes': [[300, 250], [640, 480]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', - 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', - } - ]; - let bid1Request = [ - { - 'bidder': 'videofy', - 'params': { - 'AV_PUBLISHERID': '123456', - 'AV_CHANNELID': '123456' - }, - 'adUnitCode': 'test1', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', - 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', - } - ]; - - it('Test 2 requests', function () { - const requests = spec.buildRequests(bid2Requests); - expect(requests.length).to.equal(2); - const r1 = requests[0]; - const d1 = requests[0].data; - expect(d1).to.have.property('AV_PUBLISHERID'); - expect(d1.AV_PUBLISHERID).to.equal('123456'); - expect(d1).to.have.property('AV_CHANNELID'); - expect(d1.AV_CHANNELID).to.equal('123456'); - expect(d1).to.have.property('AV_WIDTH'); - expect(d1.AV_WIDTH).to.equal(300); - expect(d1).to.have.property('AV_HEIGHT'); - expect(d1.AV_HEIGHT).to.equal(250); - expect(d1).to.have.property('AV_URL'); - expect(d1).to.have.property('cb'); - expect(d1).to.have.property('s2s'); - expect(d1.s2s).to.equal('1'); - expect(d1).to.have.property('pbjs'); - expect(d1.pbjs).to.equal(1); - expect(r1).to.have.property('url'); - expect(r1.url).to.contain('https://servx.srv-mars.com/api/adserver/vast3/'); - const r2 = requests[1]; - const d2 = requests[1].data; - expect(d2).to.have.property('AV_PUBLISHERID'); - expect(d2.AV_PUBLISHERID).to.equal('123456'); - expect(d2).to.have.property('AV_CHANNELID'); - expect(d2.AV_CHANNELID).to.equal('123456'); - expect(d2).to.have.property('AV_WIDTH'); - expect(d2.AV_WIDTH).to.equal(640); - expect(d2).to.have.property('AV_HEIGHT'); - expect(d2.AV_HEIGHT).to.equal(480); - expect(d2).to.have.property('AV_URL'); - expect(d2).to.have.property('cb'); - expect(d2).to.have.property('s2s'); - expect(d2.s2s).to.equal('1'); - expect(d2).to.have.property('pbjs'); - expect(d2.pbjs).to.equal(1); - expect(r2).to.have.property('url'); - expect(r2.url).to.contain('https://servx.srv-mars.com/api/adserver/vast3/'); - }); - - it('Test 1 request', function () { - const requests = spec.buildRequests(bid1Request); - expect(requests.length).to.equal(1); - const r = requests[0]; - const d = requests[0].data; - expect(d).to.have.property('AV_PUBLISHERID'); - expect(d.AV_PUBLISHERID).to.equal('123456'); - expect(d).to.have.property('AV_CHANNELID'); - expect(d.AV_CHANNELID).to.equal('123456'); - expect(d).to.have.property('AV_WIDTH'); - expect(d.AV_WIDTH).to.equal(640); - expect(d).to.have.property('AV_HEIGHT'); - expect(d.AV_HEIGHT).to.equal(480); - expect(d).to.have.property('AV_URL'); - expect(d).to.have.property('cb'); - expect(d).to.have.property('s2s'); - expect(d.s2s).to.equal('1'); - expect(d).to.have.property('pbjs'); - expect(d.pbjs).to.equal(1); - expect(r).to.have.property('url'); - expect(r.url).to.contain('https://servx.srv-mars.com/api/adserver/vast3/'); - }); - }); - - describe('interpretResponse', function () { - let bidRequest = { - 'url': 'https://servx.srv-mars.com/api/adserver/vast3/', - 'data': { - 'bidId': '253dcb69fb2577', - AV_PUBLISHERID: '55b78633181f4603178b4568', - AV_CHANNELID: '55b7904d181f46410f8b4568', - } - }; - let serverResponse = {}; - serverResponse.body = 'FORDFORD00:00:15'; - - it('Check bid interpretResponse', function () { - const BIDDER_CODE = 'videofy'; - let bidResponses = spec.interpretResponse(serverResponse, bidRequest); - expect(bidResponses.length).to.equal(1); - let bidResponse = bidResponses[0]; - expect(bidResponse.requestId).to.equal(bidRequest.data.bidId); - expect(bidResponse.bidderCode).to.equal(BIDDER_CODE); - expect(bidResponse.cpm).to.equal('2'); - expect(bidResponse.ttl).to.equal(600); - expect(bidResponse.currency).to.equal('USD'); - expect(bidResponse.netRevenue).to.equal(true); - expect(bidResponse.mediaType).to.equal('video'); - }); - - it('safely handles XML parsing failure from invalid bid response', function () { - let invalidServerResponse = {}; - invalidServerResponse.body = ''; - - let result = spec.interpretResponse(invalidServerResponse, bidRequest); - expect(result.length).to.equal(0); - }); - - it('handles nobid responses', function () { - let nobidResponse = {}; - nobidResponse.body = ''; - - let result = spec.interpretResponse(nobidResponse, bidRequest); - expect(result.length).to.equal(0); - }); - }); - - describe('getUserSyncs', function () { - it('Check get sync pixels from response', function () { - let pixelUrl = 'https://sync.pixel.url/sync'; - let pixelEvent = 'inventory'; - let pixelType = '3'; - let pixelStr = '{"url":"' + pixelUrl + '", "e":"' + pixelEvent + '", "t":' + pixelType + '}'; - let bidResponse = 'FORDFORD00:00:15'; - let serverResponse = [ - {body: bidResponse} - ]; - let syncPixels = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); - expect(syncPixels.length).to.equal(1); - let pixel = syncPixels[0]; - expect(pixel.url).to.equal(pixelUrl); - expect(pixel.type).to.equal('iframe'); - }); - }); - - describe('on bidWon', function () { - beforeEach(function() { - sinon.stub(utils, 'triggerPixel'); - }); - afterEach(function() { - utils.triggerPixel.restore(); - }); - it('exists and is a function', () => { - expect(spec.onBidWon).to.exist.and.to.be.a('function'); - }); - it('should return nothing', function () { - var response = spec.onBidWon({}); - expect(response).to.be.an('undefined') - expect(utils.triggerPixel.called).to.equal(true); - }); - }); - - describe('on Timeout', function () { - beforeEach(function() { - sinon.stub(utils, 'triggerPixel'); - }); - afterEach(function() { - utils.triggerPixel.restore(); - }); - it('exists and is a function', () => { - expect(spec.onTimeout).to.exist.and.to.be.a('function'); - }); - it('should return nothing', function () { - var response = spec.onTimeout({}); - expect(response).to.be.an('undefined') - expect(utils.triggerPixel.called).to.equal(true); - }); - }); - - describe('on Set Targeting', function () { - beforeEach(function() { - sinon.stub(utils, 'triggerPixel'); - }); - afterEach(function() { - utils.triggerPixel.restore(); - }); - it('exists and is a function', () => { - expect(spec.onSetTargeting).to.exist.and.to.be.a('function'); - }); - it('should return nothing', function () { - var response = spec.onSetTargeting({}); - expect(response).to.be.an('undefined') - expect(utils.triggerPixel.called).to.equal(true); - }); - }); -}); diff --git a/test/spec/modules/videoreachBidAdapter_spec.js b/test/spec/modules/videoreachBidAdapter_spec.js index e5c6b9b30ad..67ad89eac3d 100644 --- a/test/spec/modules/videoreachBidAdapter_spec.js +++ b/test/spec/modules/videoreachBidAdapter_spec.js @@ -91,7 +91,8 @@ describe('videoreachBidAdapter', function () { 'creativeId': '5cb5dc9375c0e', 'netRevenue': true, 'currency': 'EUR', - 'sync': ['https:\/\/SYNC_URL'] + 'sync': ['https:\/\/SYNC_URL'], + 'adomain': [] }] } }; @@ -107,7 +108,10 @@ describe('videoreachBidAdapter', function () { ttl: 360, ad: '', requestId: '242d506d4e4f15', - creativeId: '5cb5dc9375c0e' + creativeId: '5cb5dc9375c0e', + meta: { + advertiserDomains: [] + } } ]; diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js new file mode 100644 index 00000000000..8aa127faef2 --- /dev/null +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -0,0 +1,235 @@ +import { expect } from 'chai'; +import { spec } from 'modules/vidoomyBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { INSTREAM } from '../../../src/video'; + +const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`; +const PIXELS = ['/test.png', '/test2.png?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}'] + +describe('vidoomyBidAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid; + beforeEach(() => { + bid = { + 'bidder': 'vidoomy', + 'params': { + pid: '123123', + id: '123123', + bidfloor: 0.5 + }, + 'adUnitCode': 'code', + 'sizes': [[300, 250]] + }; + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when pid is empty', function () { + bid.params.pid = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when bidfloor is invalid', function () { + bid.params.bidfloor = 'not a number'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when id is empty', function () { + bid.params.id = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when mediaType is video with INSTREAM context and lacks playerSize property', function () { + bid.params.mediaTypes = { + video: { + context: INSTREAM + } + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'vidoomy', + 'params': { + pid: '123123', + id: '123123' + }, + 'adUnitCode': 'code', + 'mediaTypes': { + 'banner': { + 'context': 'outstream', + 'sizes': [[300, 250], [200, 100]] + } + }, + }, + { + 'bidder': 'vidoomy', + 'params': { + pid: '456456', + id: '456456' + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'playerSize': [400, 225], + } + }, + 'adUnitCode': 'code2', + } + ]; + + let bidderRequest = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://example.com', + stack: ['http://example.com'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('sends bid request to our endpoint via GET', function () { + expect(request[0].method).to.equal('GET'); + expect(request[1].method).to.equal('GET'); + }); + + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.equal(ENDPOINT); + expect(request[1].url).to.equal(ENDPOINT); + }); + + it('only accepts first width and height sizes', function () { + expect('' + request[0].data.w).to.equal('300'); + expect('' + request[0].data.h).to.equal('250'); + expect('' + request[0].data.w).to.not.equal('200'); + expect('' + request[0].data.h).to.not.equal('100'); + expect('' + request[1].data.w).to.equal('400'); + expect('' + request[1].data.h).to.equal('225'); + }); + + it('should send id and pid parameters', function () { + expect('' + request[0].data.id).to.equal('123123'); + expect('' + request[0].data.pid).to.equal('123123'); + expect('' + request[1].data.id).to.equal('456456'); + expect('' + request[1].data.pid).to.equal('456456'); + }); + }); + + describe('interpretResponse', function () { + const serverResponseVideo = { + body: { + 'vastUrl': 'https:\/\/vpaid.vidoomy.com\/demo-ad\/tag.xml', + 'mediaType': 'video', + 'requestId': '123123', + 'cpm': 3.265, + 'currency': 'USD', + 'width': 0, + 'height': 300, + 'creativeId': '123123', + 'dealId': '23cb20aa264b72', + 'netRevenue': true, + 'ttl': 60, + 'meta': { + 'mediaType': 'video', + 'rendererUrl': 'https:\/\/vpaid.vidoomy.com\/outstreamplayer\/bundle.js', + 'advertiserDomains': ['vidoomy.com'], + 'advertiserId': 123, + 'advertiserName': 'Vidoomy', + 'agencyId': null, + 'agencyName': null, + 'brandId': null, + 'brandName': null, + 'dchain': null, + 'networkId': null, + 'networkName': null, + 'primaryCatId': 'IAB3-1', + 'secondaryCatIds': null + } + } + } + + const serverResponseBanner = { + body: { + 'ad': '` + + `` + + `');` + } + } + } + }); + + after(() => { + serverResponses = undefined; + }); + + it('for only iframe enabled syncs', () => { + let syncOptions = { + iframeEnabled: true, + pixelEnabled: false + }; + let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); + expect(pixelsObjects.length).to.equal(2); + expect(pixelsObjects).to.deep.equal( + [ + {type: 'iframe', 'url': IFRAME_ONE_URL}, + {type: 'iframe', 'url': IFRAME_TWO_URL} + ] + ) + }); + + it('for only pixel enabled syncs', () => { + let syncOptions = { + iframeEnabled: false, + pixelEnabled: true + }; + let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); + expect(pixelsObjects.length).to.equal(1); + expect(pixelsObjects).to.deep.equal( + [ + {type: 'image', 'url': IMAGE_PIXEL_URL} + ] + ) + }); + + it('for both pixel and iframe enabled syncs', () => { + let syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); + expect(pixelsObjects.length).to.equal(3); + expect(pixelsObjects).to.deep.equal( + [ + {type: 'iframe', 'url': IFRAME_ONE_URL}, + {type: 'image', 'url': IMAGE_PIXEL_URL}, + {type: 'iframe', 'url': IFRAME_TWO_URL} + ] + ) + }); + }); + + // Validate Bid Requests + describe('isBidRequestValid()', () => { + const INVALID_INPUT = [ + {}, + {params: {}}, + {params: {dcn: '2c9d2b50015a5aa95b70a9b0b5b10012'}}, + {params: {dcn: 1234, pos: 'header'}}, + {params: {dcn: '2c9d2b50015a5aa95b70a9b0b5b10012', pos: 1234}}, + {params: {dcn: '2c9d2b50015a5aa95b70a9b0b5b10012', pos: ''}}, + {params: {dcn: '', pos: 'header'}}, + ]; + + INVALID_INPUT.forEach(input => { + it(`should determine that the bid is INVALID for the input ${JSON.stringify(input)}`, () => { + expect(spec.isBidRequestValid(input)).to.be.false; + }); + }); + + it('should determine that the bid is VALID if dcn and pos are present on the params object', () => { + const validBid = { + params: { + dcn: '2c9d2b50015a5aa95b70a9b0b5b10012', + pos: 'header' + } + }; + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + + it('should mark bid as VALID if bid.params.testing.e2etest = "true" (dcn & pos not required)', () => { + const validBid = { + params: { + dcn: 8888, + pos: 9999, + testing: { + e2etest: true + } + } + }; + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + + it('should mark bid ad VALID if pubId exists instead of dcn & pos', () => { + const validBid = { + params: { + pubId: DEFAULT_PUBID + } + }; + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + }); + + describe('Price Floor module support:', () => { + it('should get bidfloor from getFloor method', () => { + const { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); + bidRequest.params.bidOverride = {cur: 'EUR'}; + bidRequest.getFloor = (floorObj) => { + return { + floor: bidRequest.floors.values[floorObj.mediaType + '|640x480'], + currency: floorObj.currency, + mediaType: floorObj.mediaType + } + }; + bidRequest.floors = { + currency: 'EUR', + values: { + 'banner|640x480': 5.55 + } + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.cur).to.deep.equal(['EUR']); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(5.55); + }); + }); + + describe('Schain module support:', () => { + it('should send Global or Bidder specific schain', function () { + const { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const globalSchain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'some-platform.com', + sid: '111111', + rid: bidRequest.bidId, + hp: 1 + }] + }; + bidRequest.schain = globalSchain; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const schain = data.source.ext.schain; + expect(schain.nodes.length).to.equal(1); + expect(schain).to.equal(globalSchain); + }); + }); + + describe('First party data module - "Site" support (ortb2):', () => { + // Should not allow invalid "site" data types + const INVALID_ORTB2_TYPES = [ null, [], 123, 'unsupportedKeyName', true, false, undefined ]; + INVALID_ORTB2_TYPES.forEach(param => { + const ortb2 = { site: param } + config.setConfig({ortb2}); + it(`should not allow invalid site types to be added to bid-request: ${JSON.stringify(param)}`, () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site[param]).to.be.undefined; + }); + }); + + // Should add valid "site" params + const VALID_SITE_STRINGS = ['name', 'domain', 'page', 'ref', 'keywords', 'search']; + const VALID_SITE_ARRAYS = ['cat', 'sectioncat', 'pagecat']; + + VALID_SITE_STRINGS.forEach(param => { + it(`should allow supported site keys to be added bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + [param]: 'something' + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site[param]).to.exist; + expect(data.site[param]).to.be.a('string'); + expect(data.site[param]).to.be.equal(ortb2.site[param]); + }); + }); + + VALID_SITE_ARRAYS.forEach(param => { + it(`should determine that the ortb2.site Array key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + [param]: ['something'] + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site[param]).to.exist; + expect(data.site[param]).to.be.a('array'); + expect(data.site[param]).to.be.equal(ortb2.site[param]); + }); + }); + + // Should not allow invalid "site.content" data types + INVALID_ORTB2_TYPES.forEach(param => { + it(`should determine that the ortb2.site.content key is invalid and should not be added to bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + content: param + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.content).to.be.undefined; + }); + }); + + // Should not allow invalid "site.content" keys + it(`should not allow invalid ortb2.site.content object keys to be added to bid-request: {custom object}`, () => { + const ortb2 = { + site: { + content: { + fake: 'news', + unreal: 'param', + counterfit: 'data' + } + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.content).to.be.a('object'); + }); + + // Should append valid "site.content" keys + const VALID_CONTENT_STRINGS = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language']; + VALID_CONTENT_STRINGS.forEach(param => { + it(`should determine that the ortb2.site String key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + content: { + [param]: 'something' + } + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.content[param]).to.exist; + expect(data.site.content[param]).to.be.a('string'); + expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); + }); + }); + + const VALID_CONTENT_NUMBERS = ['episode', 'prodq', 'context', 'livestream', 'len']; + VALID_CONTENT_NUMBERS.forEach(param => { + it(`should determine that the ortb2.site.content Number key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + content: { + [param]: 1234 + } + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.content[param]).to.be.a('number'); + expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); + }); + }); + + const VALID_CONTENT_ARRAYS = ['cat']; + VALID_CONTENT_ARRAYS.forEach(param => { + it(`should determine that the ortb2.site Array key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + content: { + [param]: ['something', 'something-else'] + } + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.content[param]).to.be.a('array'); + expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); + }); + }); + + const VALID_CONTENT_OBJECTS = ['ext']; + VALID_CONTENT_OBJECTS.forEach(param => { + it(`should determine that the ortb2.site.content Object key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + content: { + [param]: {a: '123', b: '456'} + } + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.content[param]).to.be.a('object'); + expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); + config.setConfig({ortb2: {}}); + }); + }); + }); + + describe('First party data module - "User" support (ortb2):', () => { + // Global ortb2.user validations + // Should not allow invalid "user" data types + const INVALID_ORTB2_TYPES = [ null, [], 'unsupportedKeyName', true, false, undefined ]; + INVALID_ORTB2_TYPES.forEach(param => { + const ortb2 = { user: param } + config.setConfig({ortb2}); + it(`should not allow invalid site types to be added to bid-request: ${JSON.stringify(param)}`, () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.user[param]).to.be.undefined; + }); + }); + + // Should add valid "user" params + const VALID_USER_STRINGS = ['id', 'buyeruid', 'gender', 'keywords', 'customdata']; + VALID_USER_STRINGS.forEach(param => { + it(`should allow supported user string keys to be added bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + user: { + [param]: 'something' + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.user[param]).to.exist; + expect(data.user[param]).to.be.a('string'); + expect(data.user[param]).to.be.equal(ortb2.user[param]); + }); + }); + + const VALID_USER_NUMBERS = ['yob']; + VALID_USER_NUMBERS.forEach(param => { + it(`should allow supported user number keys to be added bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + user: { + [param]: 1982 + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.user[param]).to.exist; + expect(data.user[param]).to.be.a('number'); + expect(data.user[param]).to.be.equal(ortb2.user[param]); + }); + }); + + const VALID_USER_ARRAYS = ['data']; + VALID_USER_ARRAYS.forEach(param => { + it(`should allow supported user Array keys to be added to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + user: { + [param]: ['something'] + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.user[param]).to.exist; + expect(data.user[param]).to.be.a('array'); + expect(data.user[param]).to.be.equal(ortb2.user[param]); + }); + }); + + const VALID_USER_OBJECTS = ['ext']; + VALID_USER_OBJECTS.forEach(param => { + it(`should allow supported user extObject keys to be added to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + user: { + [param]: {a: '123', b: '456'} + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.user[param]).to.be.a('object'); + expect(data.user[param]).to.be.deep.include({[param]: {a: '123', b: '456'}}); + config.setConfig({ortb2: {}}); + }); + }); + + // Should allow valid user.data && site.content.data + const VALID_USER_DATA_STRINGS = ['id', 'name']; + VALID_USER_DATA_STRINGS.forEach(param => { + it(`should allow supported user.data & site.content.data strings to be added to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + user: { + data: [{[param]: 'string'}] + }, + site: { + content: { + data: [{[param]: 'string'}] + } + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.user.data[0][param]).to.exist; + expect(data.user.data[0][param]).to.be.a('string'); + expect(data.user.data[0][param]).to.be.equal(ortb2.user.data[0][param]); + expect(data.site.content.data[0][param]).to.exist; + expect(data.site.content.data[0][param]).to.be.a('string'); + expect(data.site.content.data[0][param]).to.be.equal(ortb2.site.content.data[0][param]); + }); + }); + + const VALID_USER_DATA_ARRAYS = ['segment'] + VALID_USER_DATA_ARRAYS.forEach(param => { + it(`should allow supported user data arrays to be added to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + user: { + data: [{[param]: [{id: 1}]}] + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.user.data[0][param]).to.exist; + expect(data.user.data[0][param]).to.be.a('array'); + expect(data.user.data[0][param]).to.be.equal(ortb2.user.data[0][param]); + }); + }); + + const VALID_USER_DATA_OBJECTS = ['ext']; + VALID_USER_DATA_OBJECTS.forEach(param => { + it(`should allow supported user data objects to be added to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + user: { + data: [{[param]: {id: 'ext'}}] + } + }; + config.setConfig({ortb2}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.user.data[0][param]).to.exist; + expect(data.user.data[0][param]).to.be.a('object'); + expect(data.user.data[0][param]).to.be.equal(ortb2.user.data[0][param]); + config.setConfig({ortb2: {}}); + }); + }); + + // adUnit.ortb2Imp.ext.data + it(`should allow adUnit.ortb2Imp.ext.data object to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'homepage-top-rect', + adUnitSpecificAttribute: '123' + } + } + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].ext.data).to.deep.equal(validBidRequests[0].ortb2Imp.ext.data); + }); + // adUnit.ortb2Imp.instl + it(`should allow adUnit.ortb2Imp.instl numeric boolean "1" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: 1 + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.deep.equal(validBidRequests[0].ortb2Imp.instl); + }); + + it(`should prevent adUnit.ortb2Imp.instl boolean "true" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: true + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.not.exist; + }); + + it(`should prevent adUnit.ortb2Imp.instl boolean "false" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: false + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.not.exist; + }); + }); + + describe('e2etest mode support:', () => { + it(`should override DCN & POS when params.testing.e2etest = "true".`, () => { + const { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const params = { + dcn: '1234', + pos: '5678', + testing: { + e2etest: true + } + } + bidRequest.params = params; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.id).to.be.equal('8a969516017a7a396ec539d97f540011'); + expect(data.imp[0].tagid).to.be.equal('8a969978017a7aaabab4ab0bc01a0009'); + expect(data.imp[0].ext.e2eTestMode).to.be.true; + }); + }); + + describe('GDPR & Consent:', () => { + it('should return request objects that do not send cookies if purpose 1 consent is not provided', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + bidderRequest.gdprConsent = { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + apiVersion: 2, + vendorData: { + purpose: { + consents: { + '1': false + } + } + }, + gdprApplies: true + }; + const options = spec.buildRequests(validBidRequests, bidderRequest)[0].options; + expect(options.withCredentials).to.be.false; + }); + }); + + describe('Endpoint & Impression Request Mode:', () => { + it('should route request to config override endpoint', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const testOverrideEndpoint = 'http://foo.bar.baz.com/bidderRequest'; + config.setConfig({ + yahoossp: { + endpoint: testOverrideEndpoint + } + }); + const response = spec.buildRequests(validBidRequests, bidderRequest)[0]; + expect(response).to.deep.include( + { + method: 'POST', + url: testOverrideEndpoint + }); + }); + + it('should route request to /bidRequest endpoint when dcn & pos present', () => { + config.setConfig({ + yahoossp: {} + }); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const response = spec.buildRequests(validBidRequests, bidderRequest); + expect(response[0]).to.deep.include({ + method: 'POST', + url: 'https://c2shb.pubgw.yahoo.com/bidRequest' + }); + }); + + it('should route request to /partners endpoint when pubId is present', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({pubIdMode: true}); + const response = spec.buildRequests(validBidRequests, bidderRequest); + expect(response[0]).to.deep.include({ + method: 'POST', + url: 'https://c2shb.pubgw.yahoo.com/admax/bid/partners/PBJS' + }); + }); + + it('should return a single request object for single request mode', () => { + let { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const BID_ID_2 = '84ab50xxxxx'; + const BID_POS_2 = 'footer'; + const AD_UNIT_CODE_2 = 'test-ad-unit-code-123'; + const { bidRequest: bidRequest2 } = generateBuildRequestMock({bidId: BID_ID_2, pos: BID_POS_2, adUnitCode: AD_UNIT_CODE_2}); + validBidRequests = [bidRequest, bidRequest2]; + bidderRequest.bids = validBidRequests; + + config.setConfig({ + yahoossp: { + singleRequestMode: true + } + }); + + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + expect(data.imp).to.be.an('array').with.lengthOf(2); + + expect(data.imp[0]).to.deep.include({ + id: DEFAULT_BID_ID, + ext: { + pos: DEFAULT_BID_POS, + dfp_ad_unit_code: DEFAULT_AD_UNIT_CODE + } + }); + + expect(data.imp[1]).to.deep.include({ + id: BID_ID_2, + ext: { + pos: BID_POS_2, + dfp_ad_unit_code: AD_UNIT_CODE_2 + } + }); + }); + }); + + describe('Validate request filtering:', () => { + it('should not return request when no bids are present', function () { + let request = spec.buildRequests([]); + expect(request).to.be.undefined; + }); + + it('buildRequests(): should return an array with the correct amount of request objects', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const response = spec.buildRequests(validBidRequests, bidderRequest).bidderRequest; + expect(response.bids).to.be.an('array').to.have.lengthOf(1); + }); + }); + + describe('Request Headers validation:', () => { + it('should return request objects with the relevant custom headers and content type declaration', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const options = spec.buildRequests(validBidRequests, bidderRequest).options; + expect(options).to.deep.equal( + { + contentType: 'application/json', + customHeaders: { + 'x-openrtb-version': '2.5' + }, + withCredentials: true + }); + }); + }); + + describe('Request Payload oRTB bid validation:', () => { + it('should generate a valid openRTB bid-request object in the data field', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + expect(data.site).to.deep.equal({ + id: bidderRequest.bids[0].params.dcn, + page: bidderRequest.refererInfo.referer + }); + + expect(data.device).to.deep.equal({ + dnt: 0, + ua: navigator.userAgent, + ip: undefined + }); + + expect(data.regs).to.deep.equal({ + ext: { + 'us_privacy': '', + gdpr: 1 + } + }); + + expect(data.source).to.deep.equal({ + ext: { + hb: 1, + adapterver: ADAPTER_VERSION, + prebidver: PREBID_VERSION, + integration: { + name: INTEGRATION_METHOD, + ver: PREBID_VERSION + } + }, + fd: 1 + }); + + expect(data.user).to.deep.equal({ + ext: { + consent: bidderRequest.gdprConsent.consentString, + eids: [] + } + }); + + expect(data.cur).to.deep.equal(['USD']); + }); + + it('should generate a valid openRTB imp.ext object in the bid-request', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const bid = validBidRequests[0]; + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + expect(data.imp[0].ext).to.deep.equal({ + pos: bid.params.pos, + dfp_ad_unit_code: DEFAULT_AD_UNIT_CODE + }); + }); + + it('should use siteId value as site.id in the outbound bid-request when using "pubId" integration mode', () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({pubIdMode: true}); + validBidRequests[0].params.siteId = '1234567'; + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + expect(data.site.id).to.equal('1234567'); + }); + + it('should use placementId value as imp.tagid in the outbound bid-request when using "pubId" integration mode', () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({pubIdMode: true}); + validBidRequests[0].params.placementId = 'header-300x250'; + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + expect(data.imp[0].tagid).to.deep.equal('header-300x250'); + }); + }); + + describe('Request Payload oRTB bid.imp validation:', () => { + // Validate Banner imp imp when yahoossp.mode=undefined + it('should generate a valid "Banner" imp object', () => { + config.setConfig({ + yahoossp: {} + }); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].video).to.not.exist; + expect(data.imp[0].banner).to.deep.equal({ + mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], + format: [{w: 300, h: 250}, {w: 300, h: 600}] + }); + }); + + // Validate Banner imp when yahoossp.mode="banner" + it('should generate a valid "Banner" imp object', () => { + config.setConfig({ + yahoossp: { mode: 'banner' } + }); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].video).to.not.exist; + expect(data.imp[0].banner).to.deep.equal({ + mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], + format: [{w: 300, h: 250}, {w: 300, h: 600}] + }); + }); + + // Validate Video imp + it('should generate a valid "Video" only imp object', () => { + config.setConfig({ + yahoossp: { mode: 'video' } + }); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: 'video'}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].banner).to.not.exist; + expect(data.imp[0].video).to.deep.equal({ + mimes: ['video/mp4', 'application/javascript'], + w: 300, + h: 250, + api: [2], + protocols: [2, 5], + startdelay: 0, + linearity: 1, + maxbitrate: undefined, + maxduration: undefined, + minduration: undefined, + delivery: undefined, + pos: undefined, + playbackmethod: undefined, + rewarded: undefined, + placement: undefined + }); + }); + + // Validate multi-format Video+banner imp + it('should generate a valid multi-format "Video + Banner" imp object', () => { + config.setConfig({ + yahoossp: { mode: 'all' } + }); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: 'multi-format'}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].banner).to.deep.equal({ + mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], + format: [{w: 300, h: 250}, {w: 300, h: 600}] + }); + expect(data.imp[0].video).to.deep.equal({ + mimes: ['video/mp4', 'application/javascript'], + w: 300, + h: 250, + api: [2], + protocols: [2, 5], + startdelay: 0, + linearity: 1, + maxbitrate: undefined, + maxduration: undefined, + minduration: undefined, + delivery: undefined, + pos: undefined, + playbackmethod: undefined, + rewarded: undefined, + placement: undefined + }); + }); + + // Validate Key-Value Pairs + it('should generate supported String, Number, Array of Strings, Array of Numbers key-value pairs and append to imp.ext.kvs', () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].params.kvp = { + key1: 'String', + key2: 123456, + key3: ['String', 'String', 'String'], + key4: [1, 2, 3], + invalidKey1: true, + invalidKey2: null, + invalidKey3: ['string', 1234], + invalidKey4: {a: 1, b: 2}, + invalidKey5: undefined + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + + expect(data.imp[0].ext.kvs).to.deep.equal({ + key1: 'String', + key2: 123456, + key3: ['String', 'String', 'String'], + key4: [1, 2, 3] + }); + }); + }); + + describe('Multiple adUnit validations:', () => { + // Multiple banner adUnits + it('should generate multiple bid-requests for each adUnit - 2 banner only', () => { + config.setConfig({ + yahoossp: { mode: 'banner' } + }); + + const BID_ID_2 = '84ab50xxxxx'; + const BID_POS_2 = 'footer'; + const AD_UNIT_CODE_2 = 'test-ad-unit-code-123'; + const BID_ID_3 = '84ab50yyyyy'; + const BID_POS_3 = 'hero'; + const AD_UNIT_CODE_3 = 'video-ad-unit'; + + let { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); // banner + const { bidRequest: bidRequest2 } = generateBuildRequestMock({bidId: BID_ID_2, pos: BID_POS_2, adUnitCode: AD_UNIT_CODE_2}); // banner + const { bidRequest: bidRequest3 } = generateBuildRequestMock({bidId: BID_ID_3, pos: BID_POS_3, adUnitCode: AD_UNIT_CODE_3, adUnitType: 'video'}); // video (should be filtered) + validBidRequests = [bidRequest, bidRequest2, bidRequest3]; + bidderRequest.bids = validBidRequests; + + const response = spec.buildRequests(validBidRequests, bidderRequest) + expect(response).to.be.a('array'); + expect(response.length).to.equal(2); + response.forEach((obj) => { + expect(obj.data.imp[0].video).to.not.exist + expect(obj.data.imp[0].banner).to.deep.equal({ + mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], + format: [{w: 300, h: 250}, {w: 300, h: 600}] + }); + }); + }); + + // Multiple video adUnits + it('should generate multiple bid-requests for each adUnit - 2 video only', () => { + config.setConfig({ + yahoossp: { mode: 'video' } + }); + const BID_ID_2 = '84ab50xxxxx'; + const BID_POS_2 = 'footer'; + const AD_UNIT_CODE_2 = 'test-ad-unit-code-123'; + const BID_ID_3 = '84ab50yyyyy'; + const BID_POS_3 = 'hero'; + const AD_UNIT_CODE_3 = 'video-ad-unit'; + + let { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: 'video'}); // video + const { bidRequest: bidRequest2 } = generateBuildRequestMock({bidId: BID_ID_2, pos: BID_POS_2, adUnitCode: AD_UNIT_CODE_2, adUnitType: 'video'}); // video + const { bidRequest: bidRequest3 } = generateBuildRequestMock({bidId: BID_ID_3, pos: BID_POS_3, adUnitCode: AD_UNIT_CODE_3}); // banner (should be filtered) + validBidRequests = [bidRequest, bidRequest2, bidRequest3]; + bidderRequest.bids = validBidRequests; + + const response = spec.buildRequests(validBidRequests, bidderRequest) + expect(response).to.be.a('array'); + expect(response.length).to.equal(2); + response.forEach((obj) => { + expect(obj.data.imp[0].banner).to.not.exist + expect(obj.data.imp[0].video).to.deep.equal({ + mimes: ['video/mp4', 'application/javascript'], + w: 300, + h: 250, + api: [2], + protocols: [2, 5], + startdelay: 0, + linearity: 1, + maxbitrate: undefined, + maxduration: undefined, + minduration: undefined, + delivery: undefined, + pos: undefined, + playbackmethod: undefined, + rewarded: undefined, + placement: undefined + }); + }); + }); + // Mixed adUnits 1-banner, 1-video, 1-native (should filter out native) + it('should generate multiple bid-requests for both "video & banner" adUnits', () => { + config.setConfig({ + yahoossp: { mode: 'all' } + }); + const BID_ID_2 = '84ab50xxxxx'; + const BID_POS_2 = 'footer'; + const AD_UNIT_CODE_2 = 'video-ad-unit'; + const BID_ID_3 = '84ab50yyyyy'; + const BID_POS_3 = 'hero'; + const AD_UNIT_CODE_3 = 'native-ad-unit'; + + let { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: 'banner'}); // banner + const { bidRequest: bidRequest2 } = generateBuildRequestMock({bidId: BID_ID_2, pos: BID_POS_2, adUnitCode: AD_UNIT_CODE_2, adUnitType: 'video'}); // video + const { bidRequest: bidRequest3 } = generateBuildRequestMock({bidId: BID_ID_3, pos: BID_POS_3, adUnitCode: AD_UNIT_CODE_3, adUnitType: 'native'}); // native (should be filtered) + validBidRequests = [bidRequest, bidRequest2, bidRequest3]; + bidderRequest.bids = validBidRequests; + + const response = spec.buildRequests(validBidRequests, bidderRequest); + expect(response).to.be.a('array'); + expect(response.length).to.equal(2); + response.forEach((obj) => { + expect(obj.data.imp[0].native).to.not.exist; + }); + + const data1 = response[0].data; + expect(data1.imp[0].video).to.not.exist; + expect(data1.imp[0].banner).to.deep.equal({ + mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], + format: [{w: 300, h: 250}, {w: 300, h: 600}] + }); + + const data2 = response[1].data; + expect(data2.imp[0].banner).to.not.exist; + expect(data2.imp[0].video).to.deep.equal({ + mimes: ['video/mp4', 'application/javascript'], + w: 300, + h: 250, + api: [2], + protocols: [2, 5], + startdelay: 0, + linearity: 1, + maxbitrate: undefined, + maxduration: undefined, + minduration: undefined, + delivery: undefined, + pos: undefined, + playbackmethod: undefined, + rewarded: undefined, + placement: undefined + }); + }); + }); + + describe('Video params firstlook & bidOverride validations:', () => { + it('should first look at params.bidOverride for video placement data', () => { + config.setConfig({ + yahoossp: { mode: 'video' } + }); + const bidOverride = { + imp: { + video: { + mimes: ['video/mp4'], + w: 400, + h: 350, + api: [1], + protocols: [1, 3], + startdelay: 0, + linearity: 1, + maxbitrate: 400000, + maxduration: 3600, + minduration: 1500, + delivery: 1, + pos: 123456, + playbackmethod: 1, + rewarded: 1, + placement: 1 + } + } + } + const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: 'video', bidOverrideObject: bidOverride}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].video).to.deep.equal(bidOverride.imp.video); + }); + + it('should second look at bid.mediaTypes.video for video placement data', () => { + config.setConfig({ + yahoossp: { mode: 'video' } + }); + let { bidRequest, bidderRequest } = generateBuildRequestMock({adUnitType: 'video'}); + bidRequest.mediaTypes.video = { + mimes: ['video/mp4'], + playerSize: [400, 350], + api: [1], + protocols: [1, 3], + startdelay: 0, + linearity: 1, + maxbitrate: 400000, + maxduration: 3600, + minduration: 1500, + delivery: 1, + pos: 123456, + playbackmethod: 1, + placement: 1 + } + const validBidRequests = [bidRequest]; + bidderRequest.bids = validBidRequests; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].video).to.deep.equal({ + mimes: ['video/mp4'], + w: 400, + h: 350, + api: [1], + protocols: [1, 3], + startdelay: 0, + linearity: 1, + maxbitrate: 400000, + maxduration: 3600, + minduration: 1500, + delivery: 1, + pos: 123456, + playbackmethod: 1, + placement: 1, + rewarded: undefined + }); + }); + + it('should use params.bidOverride.device.ip override', () => { + config.setConfig({ + yahoossp: { mode: 'all' } + }); + const bidOverride = { + device: { + ip: '1.2.3.4' + } + } + const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: 'video', bidOverrideObject: bidOverride}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.device.ip).to.deep.equal(bidOverride.device.ip); + }); + }); + // #endregion buildRequests(): + + describe('interpretResponse()', () => { + describe('for mediaTypes: "banner"', () => { + it('should insert banner payload into response[0].ad', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ad).to.equal(''); + expect(response[0].mediaType).to.equal('banner'); + }) + }); + + describe('for mediaTypes: "video"', () => { + it('should insert video VPAID payload into vastXml', () => { + const { serverResponse, bidderRequest } = generateResponseMock('video'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ad).to.be.undefined; + expect(response[0].vastXml).to.equal(''); + expect(response[0].mediaType).to.equal('video'); + }) + + it('should insert video VAST win notification into vastUrl', () => { + const { serverResponse, bidderRequest } = generateResponseMock('video', 'vast'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ad).to.be.undefined; + expect(response[0].vastUrl).to.equal('https://yahoo.com?event=adAttempt'); + expect(response[0].vastXml).to.equal(''); + expect(response[0].mediaType).to.equal('video'); + }) + + it('should insert video DAP O2 Player into ad', () => { + const { serverResponse, bidderRequest } = generateResponseMock('dap-o2', 'vpaid'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ad).to.equal(''); + expect(response[0].vastUrl).to.be.undefined; + expect(response[0].vastXml).to.be.undefined; + expect(response[0].mediaType).to.equal('banner'); + }); + + it('should insert video DAP Unified Player into ad', () => { + const { serverResponse, bidderRequest } = generateResponseMock('dap-up', 'vpaid'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ad).to.equal(''); + expect(response[0].vastUrl).to.be.undefined; + expect(response[0].vastXml).to.be.undefined; + expect(response[0].mediaType).to.equal('banner'); + }) + }); + + describe('Support Advertiser domains', () => { + it('should append bid-response adomain to meta.advertiserDomains', () => { + const { serverResponse, bidderRequest } = generateResponseMock('video', 'vpaid'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].meta.advertiserDomains).to.be.a('array'); + expect(response[0].meta.advertiserDomains[0]).to.equal('advertiser-domain.com'); + }) + }); + + describe('bid response Ad ID / Creative ID', () => { + it('should use adId if it exists in the bid-response', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const adId = 'bid-response-adId'; + serverResponse.body.seatbid[0].bid[0].adId = adId; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].adId).to.equal(adId); + }); + + it('should use impid if adId does not exist in the bid-response', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const impid = '25b6c429c1f52f'; + serverResponse.body.seatbid[0].bid[0].impid = impid; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].adId).to.equal(impid); + }); + + it('should use crid if adId & impid do not exist in the bid-response', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const crid = 'passback-12579'; + serverResponse.body.seatbid[0].bid[0].impid = undefined; + serverResponse.body.seatbid[0].bid[0].crid = crid; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].adId).to.equal(crid); + }); + }); + + describe('Time To Live (ttl)', () => { + const UNSUPPORTED_TTL_FORMATS = ['string', [1, 2, 3], true, false, null, undefined]; + UNSUPPORTED_TTL_FORMATS.forEach(param => { + it('should not allow unsupported global yahoossp.ttl formats and default to 300', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + config.setConfig({ + yahoossp: { ttl: param } + }); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); + }); + + it('should not allow unsupported params.ttl formats and default to 300', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + bidderRequest.bids[0].params.ttl = param; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); + }); + }); + + const UNSUPPORTED_TTL_VALUES = [-1, 3601]; + UNSUPPORTED_TTL_VALUES.forEach(param => { + it('should not allow invalid global yahoossp.ttl values 3600 < ttl < 0 and default to 300', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + config.setConfig({ + yahoossp: { ttl: param } + }); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); + }); + + it('should not allow invalid params.ttl values 3600 < ttl < 0 and default to 300', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + bidderRequest.bids[0].params.ttl = param; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); + }); + }); + + it('should give presedence to Gloabl ttl over params.ttl ', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + config.setConfig({ + yahoossp: { ttl: 500 } + }); + bidderRequest.bids[0].params.ttl = 400; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(500); + }); + }); + }); +}); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 6dde671578c..f80cad46d50 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -1,3 +1,4 @@ +import { config } from 'src/config.js'; import { expect } from 'chai' import { spec } from 'modules/yieldlabBidAdapter.js' import { newBidder } from 'src/adapters/bidderFactory.js' @@ -16,7 +17,22 @@ const REQUEST = { 'extraParam': true, 'foo': 'bar' }, - 'extId': 'abc' + 'extId': 'abc', + 'iabContent': { + 'id': 'foo_id', + 'episode': '99', + 'title': 'foo_title,bar_title', + 'series': 'foo_series', + 'season': 's1', + 'artist': 'foo bar', + 'genre': 'baz', + 'isrc': 'CC-XXX-YY-NNNNN', + 'url': 'http://foo_url.de', + 'cat': ['cat1', 'cat2,ppp', 'cat3|||//'], + 'context': '7', + 'keywords': ['k1,', 'k2..'], + 'live': '0' + } }, 'bidderRequestId': '143346cf0f1731', 'auctionId': '2e41f65424c87c', @@ -49,6 +65,20 @@ const REQUEST = { } } +const VIDEO_REQUEST = Object.assign({}, REQUEST, { + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } +}) + +const NATIVE_REQUEST = Object.assign({}, REQUEST, { + 'mediaTypes': { + 'native': { } + } +}) + const RESPONSE = { advertiser: 'yieldlab', curl: 'https://www.yieldlab.de', @@ -60,10 +90,50 @@ const RESPONSE = { adtype: 'BANNER' } +const NATIVE_RESPONSE = Object.assign({}, RESPONSE, { + 'adtype': 'NATIVE', + 'native': { + 'link': { + 'url': 'https://www.yieldlab.de' + }, + 'assets': [ + { + 'id': 1, + 'title': { + 'text': 'This is a great headline' + } + }, + { + 'id': 2, + 'img': { + 'url': 'https://localhost:8080/yl-logo100x100.jpg', + 'w': 100, + 'h': 100 + } + }, + { + 'id': 3, + 'data': { + 'value': 'Native body value' + } + } + ], + 'imptrackers': [ + 'http://localhost:8080/ve?d=ODE9ZSY2MTI1MjAzNjMzMzYxPXN0JjA0NWUwZDk0NTY5Yi05M2FiLWUwZTQtOWFjNy1hYWY0MzFiZj1kaXQmMj12', + 'http://localhost:8080/md/1111/9efa4e76-2030-4f04-bb9f-322541f8d611?mdata=false&pvid=false&ids=x:1', + 'http://localhost:8080/imp?s=13216&d=2171514&a=12548955&ts=1633363025216&tid=fb134faa-7ca9-4e0e-ba39-b96549d0e540&l=0' + ] + } +}) + const VIDEO_RESPONSE = Object.assign({}, RESPONSE, { 'adtype': 'VIDEO' }) +const PVID_RESPONSE = Object.assign({}, VIDEO_RESPONSE, { + 'pvid': '43513f11-55a0-4a83-94e5-0ebc08f54a2c' +}) + const REQPARAMS = { json: true, ts: 1234567890 @@ -74,6 +144,10 @@ const REQPARAMS_GDPR = Object.assign({}, REQPARAMS, { consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA' }) +const REQPARAMS_IAB_CONTENT = Object.assign({}, REQPARAMS, { + iab_content: 'id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0' +}) + describe('yieldlabBidAdapter', function () { const adapter = newBidder(spec) @@ -127,6 +201,40 @@ describe('yieldlabBidAdapter', function () { expect(request.url).to.include('schain=1.0,1!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') }) + it('passes iab_content string to bid request', function () { + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') + }) + + const siteConfig = { + 'ortb2': { + 'site': { + 'content': { + 'id': 'id_from_config' + } + } + } + } + + it('generates iab_content string from bidder params', function () { + config.setConfig(siteConfig); + const request = spec.buildRequests([REQUEST]) + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') + config.resetConfig(); + }) + + it('generates iab_content string from first party data if not provided in bidder params', function () { + const requestWithoutIabContent = { + 'params': { + 'adslotId': '1111', + 'supplyId': '2222' + } + } + config.setConfig(siteConfig); + const request = spec.buildRequests([requestWithoutIabContent]) + expect(request.url).to.include('iab_content=id%3Aid_from_config') + config.resetConfig(); + }) + const refererRequest = spec.buildRequests(bidRequests, { refererInfo: { canonicalUrl: undefined, @@ -179,6 +287,7 @@ describe('yieldlabBidAdapter', function () { expect(result[0].netRevenue).to.equal(false) expect(result[0].ttl).to.equal(300) expect(result[0].referrer).to.equal('') + expect(result[0].meta.advertiserDomains).to.equal('yieldlab') expect(result[0].ad).to.include('" + }); + $$PREBID_GLOBAL$$.renderAd(doc, bidId); + var message = 'Calling renderAd with adId :' + bidId; + sinon.assert.calledWith(spyLogMessage, message); + + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + }); + + it('should warn stale rendering', function () { + var message = 'Calling renderAd with adId :' + bidId; + var warning = `Ad id ${bidId} has been rendered before`; + var onWonEvent = sinon.stub(); + var onStaleEvent = sinon.stub(); + + $$PREBID_GLOBAL$$.onEvent(CONSTANTS.EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.onEvent(CONSTANTS.EVENTS.STALE_RENDER, onStaleEvent); + + pushBidResponseToAuction({ + ad: "" + }); + + // First render should pass with no warning and added to winning bids + $$PREBID_GLOBAL$$.renderAd(doc, bidId); + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.neverCalledWith(spyLogWarn, warning); + + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + + sinon.assert.calledWith(onWonEvent, adResponse); + sinon.assert.notCalled(onStaleEvent); + expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); + + // Reset call history for spies and stubs + spyLogMessage.resetHistory(); + spyLogWarn.resetHistory(); + spyAddWinningBid.resetHistory(); + onWonEvent.resetHistory(); + onStaleEvent.resetHistory(); + + // Second render should have a warning but still added to winning bids + $$PREBID_GLOBAL$$.renderAd(doc, bidId); + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.calledWith(spyLogWarn, warning); + + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + + sinon.assert.calledWith(onWonEvent, adResponse); + sinon.assert.calledWith(onStaleEvent, adResponse); + + // Clean up + $$PREBID_GLOBAL$$.offEvent(CONSTANTS.EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.offEvent(CONSTANTS.EVENTS.STALE_RENDER, onStaleEvent); + }); + + it('should stop stale rendering', function () { + var message = 'Calling renderAd with adId :' + bidId; + var warning = `Ad id ${bidId} has been rendered before`; + var onWonEvent = sinon.stub(); + var onStaleEvent = sinon.stub(); + + // Setting suppressStaleRender to true explicitly + configObj.setConfig({'auctionOptions': {'suppressStaleRender': true}}); + + $$PREBID_GLOBAL$$.onEvent(CONSTANTS.EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.onEvent(CONSTANTS.EVENTS.STALE_RENDER, onStaleEvent); + + pushBidResponseToAuction({ + ad: "" + }); + + // First render should pass with no warning and added to winning bids + $$PREBID_GLOBAL$$.renderAd(doc, bidId); + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.neverCalledWith(spyLogWarn, warning); + + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); + + sinon.assert.calledWith(onWonEvent, adResponse); + sinon.assert.notCalled(onStaleEvent); + + // Reset call history for spies and stubs + spyLogMessage.resetHistory(); + spyLogWarn.resetHistory(); + spyAddWinningBid.resetHistory(); + onWonEvent.resetHistory(); + onStaleEvent.resetHistory(); + + // Second render should have a warning and do not proceed further + $$PREBID_GLOBAL$$.renderAd(doc, bidId); + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.calledWith(spyLogWarn, warning); + + sinon.assert.notCalled(spyAddWinningBid); + + sinon.assert.notCalled(onWonEvent); + sinon.assert.calledWith(onStaleEvent, adResponse); + + // Clean up + $$PREBID_GLOBAL$$.offEvent(CONSTANTS.EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.offEvent(CONSTANTS.EVENTS.STALE_RENDER, onStaleEvent); + configObj.setConfig({'auctionOptions': {}}); + }); }); describe('requestBids', function () { @@ -1662,6 +1850,54 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250]]); expect(auctionArgs.adUnits[0].mediaTypes.banner.sizes).to.deep.equal([[300, 250]]); }); + + it('should filter mediaType pos value if not integer', function () { + let adUnit = [{ + code: 'test5', + bids: [], + sizes: [300, 250], + mediaTypes: { + banner: { + sizes: [300, 250], + pos: 'foo' + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: adUnit + }); + expect(auctionArgs.adUnits[0].mediaTypes.banner.pos).to.be.undefined; + }); + + it('should pass mediaType pos value if integer', function () { + let adUnit = [{ + code: 'test5', + bids: [], + sizes: [300, 250], + mediaTypes: { + banner: { + sizes: [300, 250], + pos: 2 + } + } + }, + { + code: 'test6', + bids: [], + sizes: [300, 250], + mediaTypes: { + banner: { + sizes: [300, 250], + pos: 0 + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: adUnit + }); + expect(auctionArgs.adUnits[0].mediaTypes.banner.pos).to.equal(2); + expect(auctionArgs.adUnits[1].mediaTypes.banner.pos).to.equal(0); + }); }); describe('negative tests for validating adUnits', function() { diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 566154f0003..8a629892785 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -1,10 +1,60 @@ import { - _sendAdToCreative -} from '../../../src/secureCreatives.js'; -import { expect } from 'chai'; + _sendAdToCreative, getReplier, receiveMessage +} from 'src/secureCreatives.js'; +import * as secureCreatives from 'src/secureCreatives.js'; import * as utils from 'src/utils.js'; +import {getAdUnits, getBidRequests, getBidResponses} from 'test/fixtures/fixtures.js'; +import {auctionManager} from 'src/auctionManager.js'; +import * as auctionModule from 'src/auction.js'; +import * as native from 'src/native.js'; +import {fireNativeTrackers, getAllAssetsMessage} from 'src/native.js'; +import events from 'src/events.js'; +import { config as configObj } from 'src/config.js'; +import 'src/prebid.js'; + +import { expect } from 'chai'; + +var CONSTANTS = require('src/constants.json'); describe('secureCreatives', () => { + function makeEvent(ev) { + return Object.assign({origin: 'mock-origin', ports: []}, ev) + } + + describe('getReplier', () => { + it('should use source.postMessage if no MessagePort is available', () => { + const ev = { + ports: [], + source: { + postMessage: sinon.spy() + }, + origin: 'mock-origin' + }; + getReplier(ev)('test'); + sinon.assert.calledWith(ev.source.postMessage, JSON.stringify('test')); + }); + + it('should use MesagePort.postMessage if available', () => { + const ev = { + ports: [{ + postMessage: sinon.spy() + }] + } + getReplier(ev)('test'); + sinon.assert.calledWith(ev.ports[0].postMessage, JSON.stringify('test')); + }); + + it('should throw if origin is null and no MessagePort is available', () => { + const ev = { + origin: null, + ports: [], + postMessage: sinon.spy() + } + const reply = getReplier(ev); + expect(() => reply('test')).to.throw(); + }); + }); + describe('_sendAdToCreative', () => { beforeEach(function () { sinon.stub(utils, 'logError'); @@ -30,16 +80,439 @@ describe('secureCreatives', () => { cpm: '1.00', adUnitCode: 'some_dom_id' }; - const event = { - source: { postMessage: sinon.stub() }, - origin: 'origin.sf.com' - }; - - _sendAdToCreative(mockAdObject, event); - expect(JSON.parse(event.source.postMessage.args[0][0]).ad).to.equal(''); - expect(JSON.parse(event.source.postMessage.args[0][0]).adUrl).to.equal('http://creative.prebid.org/1.00'); + const reply = sinon.spy(); + _sendAdToCreative(mockAdObject, reply); + expect(reply.args[0][0].ad).to.equal(''); + expect(reply.args[0][0].adUrl).to.equal('http://creative.prebid.org/1.00'); window.googletag = oldVal; window.apntag = oldapntag; }); }); + + describe('receiveMessage', function() { + const bidId = 1; + const warning = `Ad id ${bidId} has been rendered before`; + let auction; + let adResponse = {}; + let spyAddWinningBid; + let spyLogWarn; + let stubFireNativeTrackers; + let stubGetAllAssetsMessage; + let stubEmit; + + function pushBidResponseToAuction(obj) { + adResponse = Object.assign({ + auctionId: 1, + adId: bidId, + width: 300, + height: 250, + renderer: null + }, obj); + auction.getBidsReceived = function() { + let bidsReceived = getBidResponses(); + bidsReceived.push(adResponse); + return bidsReceived; + } + auction.getAuctionId = () => 1; + } + + function resetAuction() { + $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: false }); + auction.getBidRequests = getBidRequests; + auction.getBidsReceived = getBidResponses; + auction.getAdUnits = getAdUnits; + auction.getAuctionStatus = function() { return auctionModule.AUCTION_COMPLETED } + } + + function resetHistories(...others) { + [ + spyAddWinningBid, + spyLogWarn, + stubFireNativeTrackers, + stubGetAllAssetsMessage, + stubEmit + ].forEach(s => s.resetHistory()); + + if (others && others.length > 0) { others.forEach(s => s.resetHistory()); } + } + + before(function() { + const adUnits = getAdUnits(); + const adUnitCodes = getAdUnits().map(unit => unit.code); + const bidsBackHandler = function() {}; + const timeout = 2000; + auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout: timeout}); + resetAuction(); + }); + + after(function() { + auctionManager.clearAllAuctions(); + }); + + beforeEach(function() { + spyAddWinningBid = sinon.spy(auctionManager, 'addWinningBid'); + spyLogWarn = sinon.spy(utils, 'logWarn'); + stubFireNativeTrackers = sinon.stub(native, 'fireNativeTrackers').callsFake(message => { return message.action; }); + stubGetAllAssetsMessage = sinon.stub(native, 'getAllAssetsMessage'); + stubEmit = sinon.stub(events, 'emit'); + }); + + afterEach(function() { + spyAddWinningBid.restore(); + spyLogWarn.restore(); + stubFireNativeTrackers.restore(); + stubGetAllAssetsMessage.restore(); + stubEmit.restore(); + resetAuction(); + }); + + describe('Prebid Request', function() { + it('should render', function () { + pushBidResponseToAuction({ + renderer: {render: sinon.stub(), url: 'some url'} + }); + + const data = { + adId: bidId, + message: 'Prebid Request' + }; + + const ev = makeEvent({ + data: JSON.stringify(data), + }); + + receiveMessage(ev); + + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + + expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); + }); + + it('should allow stale rendering without config', function () { + pushBidResponseToAuction({ + renderer: {render: sinon.stub(), url: 'some url'} + }); + + const data = { + adId: bidId, + message: 'Prebid Request' + }; + + const ev = makeEvent({ + data: JSON.stringify(data) + }); + + receiveMessage(ev); + + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + + expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); + + resetHistories(adResponse.renderer.render); + + receiveMessage(ev); + + sinon.assert.calledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER, adResponse); + }); + + it('should stop stale rendering with config', function () { + configObj.setConfig({'auctionOptions': {'suppressStaleRender': true}}); + + pushBidResponseToAuction({ + renderer: {render: sinon.stub(), url: 'some url'} + }); + + const data = { + adId: bidId, + message: 'Prebid Request' + }; + + const ev = makeEvent({ + data: JSON.stringify(data) + }); + + receiveMessage(ev); + + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + + expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); + + resetHistories(adResponse.renderer.render); + + receiveMessage(ev); + + sinon.assert.calledWith(spyLogWarn, warning); + sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.notCalled(adResponse.renderer.render); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER, adResponse); + + configObj.setConfig({'auctionOptions': {}}); + }); + + it('should emit AD_RENDER_FAILED if requested missing adId', () => { + const ev = makeEvent({ + data: JSON.stringify({ + message: 'Prebid Request', + adId: 'missing' + }) + }); + receiveMessage(ev); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: CONSTANTS.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + adId: 'missing' + })); + }); + + it('should emit AD_RENDER_FAILED if creative can\'t be sent to rendering frame', () => { + pushBidResponseToAuction({}); + const ev = makeEvent({ + source: { + postMessage: sinon.stub().callsFake(() => { throw new Error(); }) + }, + data: JSON.stringify({ + message: 'Prebid Request', + adId: bidId + }) + }); + receiveMessage(ev) + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION, + adId: bidId + })); + }); + }); + + describe('Prebid Native', function() { + it('Prebid native should render', function () { + pushBidResponseToAuction({}); + + const data = { + adId: bidId, + message: 'Prebid Native', + action: 'allAssetRequest' + }; + + const ev = makeEvent({ + data: JSON.stringify(data), + source: { + postMessage: sinon.stub() + }, + origin: 'any origin' + }); + + receiveMessage(ev); + + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(stubGetAllAssetsMessage); + sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); + sinon.assert.calledOnce(ev.source.postMessage); + sinon.assert.notCalled(stubFireNativeTrackers); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); + sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + }); + + it('Prebid native should allow stale rendering without config', function () { + pushBidResponseToAuction({}); + + const data = { + adId: bidId, + message: 'Prebid Native', + action: 'allAssetRequest' + }; + + const ev = makeEvent({ + data: JSON.stringify(data), + source: { + postMessage: sinon.stub() + }, + origin: 'any origin' + }); + + receiveMessage(ev); + + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(stubGetAllAssetsMessage); + sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); + sinon.assert.calledOnce(ev.source.postMessage); + sinon.assert.notCalled(stubFireNativeTrackers); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); + sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + + resetHistories(ev.source.postMessage); + + receiveMessage(ev); + + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(stubGetAllAssetsMessage); + sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); + sinon.assert.calledOnce(ev.source.postMessage); + sinon.assert.notCalled(stubFireNativeTrackers); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); + sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + }); + + it('Prebid native should allow stale rendering with config', function () { + configObj.setConfig({'auctionOptions': {'suppressStaleRender': true}}); + + pushBidResponseToAuction({}); + + const data = { + adId: bidId, + message: 'Prebid Native', + action: 'allAssetRequest' + }; + + const ev = makeEvent({ + data: JSON.stringify(data), + source: { + postMessage: sinon.stub() + }, + origin: 'any origin' + }); + + receiveMessage(ev); + + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(stubGetAllAssetsMessage); + sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); + sinon.assert.calledOnce(ev.source.postMessage); + sinon.assert.notCalled(stubFireNativeTrackers); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); + sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + + resetHistories(ev.source.postMessage); + + receiveMessage(ev); + + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(stubGetAllAssetsMessage); + sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); + sinon.assert.calledOnce(ev.source.postMessage); + sinon.assert.notCalled(stubFireNativeTrackers); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); + sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + + configObj.setConfig({'auctionOptions': {}}); + }); + + it('Prebid native should fire trackers', function () { + pushBidResponseToAuction({}); + + const data = { + adId: bidId, + message: 'Prebid Native', + action: 'click', + }; + + const ev = makeEvent({ + data: JSON.stringify(data), + source: { + postMessage: sinon.stub() + }, + origin: 'any origin' + }); + + receiveMessage(ev); + + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(stubFireNativeTrackers); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); + sinon.assert.notCalled(spyAddWinningBid); + + resetHistories(ev.source.postMessage); + + delete data.action; + ev.data = JSON.stringify(data); + receiveMessage(ev); + + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(stubFireNativeTrackers); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); + + expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); + }); + }); + + describe('Prebid Event', () => { + Object.entries({ + 'unrendered': [false, (bid) => { delete bid.status; }], + 'rendered': [true, (bid) => { bid.status = CONSTANTS.BID_STATUS.RENDERED }] + }).forEach(([test, [shouldEmit, prepBid]]) => { + describe(`for ${test} bids`, () => { + beforeEach(() => { + prepBid(adResponse); + pushBidResponseToAuction(adResponse); + }); + + it(`should${shouldEmit ? ' ' : ' not '}emit AD_RENDER_FAILED`, () => { + const event = makeEvent({ + data: JSON.stringify({ + message: 'Prebid Event', + event: CONSTANTS.EVENTS.AD_RENDER_FAILED, + adId: bidId, + info: { + reason: 'Fail reason', + message: 'Fail message', + }, + }) + }); + receiveMessage(event); + expect(stubEmit.calledWith(CONSTANTS.EVENTS.AD_RENDER_FAILED, { + adId: bidId, + bid: adResponse, + reason: 'Fail reason', + message: 'Fail message' + })).to.equal(shouldEmit); + }); + + it(`should${shouldEmit ? ' ' : ' not '}emit AD_RENDER_SUCCEEDED`, () => { + const event = makeEvent({ + data: JSON.stringify({ + message: 'Prebid Event', + event: CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, + adId: bidId, + }) + }); + receiveMessage(event); + expect(stubEmit.calledWith(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, { + adId: bidId, + bid: adResponse, + doc: null + })).to.equal(shouldEmit); + }); + }); + }); + }); + }); }); diff --git a/test/spec/userSync_spec.js b/test/spec/userSync_spec.js index 55b613ce929..910ffe7b2d6 100644 --- a/test/spec/userSync_spec.js +++ b/test/spec/userSync_spec.js @@ -401,6 +401,33 @@ describe('user sync', function () { expect(insertUserSyncIframeStub.getCall(0).args[0]).to.equal('http://example.com/iframe'); }); + it('should not fire image pixel for a bidder if iframe pixel is fired for same bidder', function() { + const userSync = newUserSync({ + config: config.getConfig('userSync'), + browserSupportsCookies: true + }); + + config.setConfig({ + userSync: { + filterSettings: { + iframe: { + bidders: ['bidderXYZ'], + filter: 'include' + } + } + } + }); + // we are registering iframe and image sync for bidderXYZ and we expect image sync not to execute. + userSync.registerSync('image', 'testBidder', 'http://testBidder.example.com/image'); + userSync.registerSync('iframe', 'bidderXYZ', 'http://bidderXYZ.example.com/iframe'); + userSync.registerSync('image', 'bidderXYZ', 'http://bidderXYZ.example.com/image'); + userSync.syncUsers(); + expect(triggerPixelStub.getCall(0)).to.not.be.null; + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://testBidder.example.com/image'); + expect(triggerPixelStub.callCount).to.equal(1); // should not be 2 for 2 registered image syncs + expect(insertUserSyncIframeStub.getCall(0).args[0]).to.equal('http://bidderXYZ.example.com/iframe'); + }); + it('should override default image syncs if setConfig used image filter', function () { const userSync = newUserSync({ config: config.getConfig('userSync'), @@ -448,8 +475,9 @@ describe('user sync', function () { userSync.registerSync('iframe', 'testBidder', 'http://example.com/iframe'); userSync.registerSync('iframe', 'bidderXYZ', 'http://example.com/iframe-blocked'); userSync.syncUsers(); - expect(triggerPixelStub.getCall(0)).to.not.be.null; - expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com'); + // expect(triggerPixelStub.getCall(0)).to.not.be.null; + expect(triggerPixelStub.getCall(0)).to.be.null;// image sync will not execute as iframe sync has executed for same bidder + // expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com'); expect(triggerPixelStub.getCall(1)).to.be.null; expect(insertUserSyncIframeStub.getCall(0).args[0]).to.equal('http://example.com/iframe'); expect(insertUserSyncIframeStub.getCall(1)).to.be.null; diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 6494ead78e7..50a95b079c9 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -2,6 +2,7 @@ import { getAdServerTargeting } from 'test/fixtures/fixtures.js'; import { expect } from 'chai'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; +import {deepEqual, waitForElementToLoad} from 'src/utils.js'; var assert = require('assert'); @@ -1177,6 +1178,13 @@ describe('Utils', function () { } expect(utils.deepEqual(obj1, obj2)).to.equal(false); }); + it('should check types if {matchTypes: true}', () => { + function Typed(obj) { + Object.assign(this, obj); + } + const obj = {key: 'value'}; + expect(deepEqual({outer: obj}, {outer: new Typed(obj)}, {checkTypes: true})).to.be.false; + }); describe('cyrb53Hash', function() { it('should return the same hash for the same string', function() { @@ -1198,4 +1206,41 @@ describe('Utils', function () { }); }); }); + + describe('waitForElementToLoad', () => { + let element; + let callbacks; + + function callback() { + callbacks++; + } + + function delay(delay = 0) { + return new Promise((resolve) => { + window.setTimeout(resolve, delay); + }) + } + + beforeEach(() => { + callbacks = 0; + element = window.document.createElement('div'); + }); + + it('should respect timeout if set', () => { + waitForElementToLoad(element, 50).then(callback); + return delay(60).then(() => { + expect(callbacks).to.equal(1); + }); + }); + + ['load', 'error'].forEach((event) => { + it(`should complete on '${event} event'`, () => { + waitForElementToLoad(element).then(callback); + element.dispatchEvent(new Event(event)); + return delay().then(() => { + expect(callbacks).to.equal(1); + }) + }); + }); + }); }); diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index 6bb214af8a0..34e9bed04b6 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -2,6 +2,8 @@ import chai from 'chai'; import { getCacheUrl, store } from 'src/videoCache.js'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; +import {auctionManager} from '../../src/auctionManager.js'; +import {AuctionIndex} from '../../src/auctionIndex.js'; const should = chai.should(); @@ -32,23 +34,6 @@ function getMockBid(bidder, auctionId, bidderRequestId) { }; } -function getMockBidRequest(bidder = 'appnexus', auctionId = '173afb6d132ba3', bidderRequestId = '3d1063078dfcc8') { - return { - 'bidderCode': bidder, - 'auctionId': auctionId, - 'bidderRequestId': bidderRequestId, - 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', - 'bids': [getMockBid(bidder, auctionId, bidderRequestId)], - 'auctionStart': 1510852447530, - 'timeout': 5000, - 'src': 's2s', - 'doneCbCallCount': 0, - 'refererInfo': { - 'referer': 'http://mytestpage.com' - } - } -} - describe('The video cache', function () { function assertError(callbackSpy) { callbackSpy.calledOnce.should.equal(true); @@ -201,13 +186,15 @@ describe('The video cache', function () { ttl: 25, customCacheKey: customKey1, requestId: '12345abc', - bidder: 'appnexus' + bidder: 'appnexus', + auctionId: '1234-56789-abcde' }, { vastXml: vastXml2, ttl: 25, customCacheKey: customKey2, requestId: 'cba54321', - bidder: 'rubicon' + bidder: 'rubicon', + auctionId: '1234-56789-abcde' }]; store(bids, function () { }); @@ -222,6 +209,7 @@ describe('The video cache', function () { ttlseconds: 25, key: customKey1, bidid: '12345abc', + aid: '1234-56789-abcde', bidder: 'appnexus' }, { type: 'xml', @@ -229,6 +217,7 @@ describe('The video cache', function () { ttlseconds: 25, key: customKey2, bidid: 'cba54321', + aid: '1234-56789-abcde', bidder: 'rubicon' }] }; @@ -236,7 +225,7 @@ describe('The video cache', function () { JSON.parse(request.requestBody).should.deep.equal(payload); }); - it('should include additional params in request payload should config.cache.vasttrack be true and bidderRequest argument was defined', () => { + it('should include additional params in request payload should config.cache.vasttrack be true - with timestamp', () => { config.setConfig({ cache: { url: 'https://prebid.adnxs.com/pbc/v1/cache', @@ -254,16 +243,32 @@ describe('The video cache', function () { ttl: 25, customCacheKey: customKey1, requestId: '12345abc', - bidder: 'appnexus' + bidder: 'appnexus', + auctionId: '1234-56789-abcde' }, { vastXml: vastXml2, ttl: 25, customCacheKey: customKey2, requestId: 'cba54321', - bidder: 'rubicon' + bidder: 'rubicon', + auctionId: '1234-56789-abcde' }]; - store(bids, function () { }, getMockBidRequest()); + const stub = sinon.stub(auctionManager, 'index'); + stub.get(() => new AuctionIndex(() => [{ + getAuctionId() { + return '1234-56789-abcde'; + }, + getAuctionStart() { + return 1510852447530; + } + }])) + try { + store(bids, function () { }); + } finally { + stub.restore(); + } + const request = server.requests[0]; request.method.should.equal('POST'); request.url.should.equal('https://prebid.adnxs.com/pbc/v1/cache'); @@ -276,6 +281,7 @@ describe('The video cache', function () { key: customKey1, bidid: '12345abc', bidder: 'appnexus', + aid: '1234-56789-abcde', timestamp: 1510852447530 }, { type: 'xml', @@ -284,6 +290,7 @@ describe('The video cache', function () { key: customKey2, bidid: 'cba54321', bidder: 'rubicon', + aid: '1234-56789-abcde', timestamp: 1510852447530 }] }; diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 3ce8ba081da..61621c7ec42 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,113 +1,103 @@ import { isValidVideoBid } from 'src/video.js'; +import {hook} from '../../src/hook.js'; +import {stubAuctionIndex} from '../helpers/indexStub.js'; describe('video.js', function () { + before(() => { + hook.ready(); + }); + it('validates valid instream bids', function () { const bid = { adId: '456xyz', vastUrl: 'http://www.example.com/vastUrl', - requestId: '123abc' + transactionId: 'au' }; - const bidRequests = [{ - bids: [{ - bidId: '123abc', - bidder: 'appnexus', - mediaTypes: { - video: { context: 'instream' } - } - }] + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'instream'} + } }]; - const valid = isValidVideoBid(bid, bidRequests); + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(true); }); it('catches invalid instream bids', function () { const bid = { - requestId: '123abc' + transactionId: 'au' }; - const bidRequests = [{ - bids: [{ - bidId: '123abc', - bidder: 'appnexus', - mediaTypes: { - video: { context: 'instream' } - } - }] + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'instream'} + } }]; - const valid = isValidVideoBid(bid, bidRequests); + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(false); }); it('catches invalid bids when prebid-cache is disabled', function () { - const bidRequests = [{ - bids: [{ - bidder: 'vastOnlyVideoBidder', - mediaTypes: { video: {} }, - }] + const adUnits = [{ + transactionId: 'au', + bidder: 'vastOnlyVideoBidder', + mediaTypes: {video: {}}, }]; - const valid = isValidVideoBid({ vastXml: 'vast' }, bidRequests); + const valid = isValidVideoBid({ transactionId: 'au', vastXml: 'vast' }, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(false); }); it('validates valid outstream bids', function () { const bid = { - requestId: '123abc', + transactionId: 'au', renderer: { url: 'render.url', render: () => true, } }; - const bidRequests = [{ - bids: [{ - bidId: '123abc', - bidder: 'appnexus', - mediaTypes: { - video: { context: 'outstream' } - } - }] + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'outstream'} + } }]; - const valid = isValidVideoBid(bid, bidRequests); + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(true); }); it('validates valid outstream bids with a publisher defined renderer', function () { const bid = { - requestId: '123abc', + transactionId: 'au', }; - const bidRequests = [{ - bids: [{ - bidId: '123abc', - bidder: 'appnexus', - mediaTypes: { - video: { - context: 'outstream', - renderer: { - url: 'render.url', - render: () => true, - } - } + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: { + context: 'outstream', } - }] + }, + renderer: { + url: 'render.url', + render: () => true, + } }]; - const valid = isValidVideoBid(bid, bidRequests); + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(true); }); it('catches invalid outstream bids', function () { const bid = { - requestId: '123abc' + transactionId: 'au', }; - const bidRequests = [{ - bids: [{ - bidId: '123abc', - bidder: 'appnexus', - mediaTypes: { - video: { context: 'outstream' } - } - }] + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'outstream'} + } }]; - const valid = isValidVideoBid(bid, bidRequests); + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(false); }); }); diff --git a/test/test_deps.js b/test/test_deps.js new file mode 100644 index 00000000000..e535154b799 --- /dev/null +++ b/test/test_deps.js @@ -0,0 +1,3 @@ +require('test/helpers/prebidGlobal.js'); +require('test/mocks/adloaderStub.js'); +require('test/mocks/xhr.js'); diff --git a/test/test_index.js b/test/test_index.js index 53d75e36176..883f4d0590c 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,6 +1,4 @@ -require('test/helpers/prebidGlobal.js'); -require('test/mocks/adloaderStub.js'); -require('test/mocks/xhr.js'); +require('./test_deps.js'); var testsContext = require.context('.', true, /_spec$/); testsContext.keys().forEach(testsContext); diff --git a/wdio.conf.js b/wdio.conf.js index 4d93f3d88d3..4865c03f339 100644 --- a/wdio.conf.js +++ b/wdio.conf.js @@ -9,7 +9,7 @@ function getCapabilities() { return platformMap[os]; } - // only IE 11, Chrome 80 & Firefox 73 run as part of functional tests + // only Chrome 80 & Firefox 73 run as part of functional tests // rest of the browsers are discarded. delete browsers['bs_chrome_79_windows_10']; delete browsers['bs_firefox_72_windows_10']; @@ -37,29 +37,29 @@ function getCapabilities() { } exports.config = { - specs: [ - './test/spec/e2e/**/*.spec.js' - ], - services: [ - ['browserstack', { - browserstackLocal: true - }] - ], - user: process.env.BROWSERSTACK_USERNAME, - key: process.env.BROWSERSTACK_ACCESS_KEY, - maxInstances: 5, // Do not increase this, since we have only 5 parallel tests in browserstack account - capabilities: getCapabilities(), - logLevel: 'silent', // put option here: info | trace | debug | warn| error | silent - bail: 0, - waitforTimeout: 60000, // Default timeout for all waitFor* commands. - connectionRetryTimeout: 60000, // Default timeout in milliseconds for request if Selenium Grid doesn't send response - connectionRetryCount: 3, // Default request retries count - framework: 'mocha', - mochaOpts: { - ui: 'bdd', - timeout: 60000, - compilers: ['js:babel-register'], - }, - // if you see error, update this to spec reporter and logLevel above to get detailed report. - reporters: ['concise'] + specs: [ + './test/spec/e2e/**/*.spec.js' + ], + services: [ + ['browserstack', { + browserstackLocal: true + }] + ], + user: process.env.BROWSERSTACK_USERNAME, + key: process.env.BROWSERSTACK_ACCESS_KEY, + maxInstances: 5, // Do not increase this, since we have only 5 parallel tests in browserstack account + capabilities: getCapabilities(), + logLevel: 'silent', // put option here: info | trace | debug | warn| error | silent + bail: 0, + waitforTimeout: 60000, // Default timeout for all waitFor* commands. + connectionRetryTimeout: 60000, // Default timeout in milliseconds for request if Selenium Grid doesn't send response + connectionRetryCount: 3, // Default request retries count + framework: 'mocha', + mochaOpts: { + ui: 'bdd', + timeout: 60000, + compilers: ['js:babel-register'], + }, + // if you see error, update this to spec reporter and logLevel above to get detailed report. + reporters: ['concise'] } diff --git a/webpack.conf.js b/webpack.conf.js index 6f0841757b0..a738a2a0868 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -1,11 +1,11 @@ var prebid = require('./package.json'); var path = require('path'); var webpack = require('webpack'); -var helpers = require('./gulpHelpers'); +var helpers = require('./gulpHelpers.js'); var RequireEnsureWithoutJsonp = require('./plugins/RequireEnsureWithoutJsonp.js'); var { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); var argv = require('yargs').argv; -var allowedModules = require('./allowedModules'); +var allowedModules = require('./allowedModules.js'); // list of module names to never include in the common bundle chunk var neverBundle = [ @@ -23,18 +23,20 @@ if (argv.analyze) { ) } -plugins.push( // this plugin must be last so it can be easily removed for karma unit tests +plugins.push( // this plugin must be last so it can be easily removed for karma unit tests new webpack.optimize.CommonsChunkPlugin({ name: 'prebid', filename: 'prebid-core.js', minChunks: function(module) { - return ( + return ( ( module.context && module.context.startsWith(path.resolve('./src')) && !(module.resource && neverBundle.some(name => module.resource.includes(name))) ) || - module.resource && (allowedModules.src.concat(['core-js'])).some( - name => module.resource.includes(path.resolve('./node_modules/' + name)) + ( + module.resource && (allowedModules.src.concat(['core-js'])).some( + name => module.resource.includes(path.resolve('./node_modules/' + name)) + ) ) ); } @@ -50,7 +52,7 @@ module.exports = { ], }, output: { - jsonpFunction: prebid.globalVarName + "Chunk" + jsonpFunction: prebid.globalVarName + 'Chunk' }, module: { rules: [